Saturday Jan 31, 2015

Programming in C: Few Tidbits #4

1. Using Wildcards in Filename Pattern Matching

Relying on *stat() API is not much of an option when using wildcards to match a filename pattern. Some of the options involve traversing a directory checking each file for a match using fnmatch(), or to use system() function to execute an equivalent shell command. Another option that is well suited for this task is the glob*() API, which is part of Standard C Library Functions. (I believe glob() depends on fnmatch() for finding matches).

Here is a simple example that displays the number of matches found for pattern "/tmp/lint_" along with the listing of matches.

% ls -1 /tmp/lint_*
/tmp/lint_AAA.21549.0vaOfQ
/tmp/lint_BAA.21549.1vaOfQ
/tmp/lint_CAA.21549.2vaOfQ
/tmp/lint_DAA.21549.3vaOfQ
/tmp/lint_EAA.21549.4vaOfQ
/tmp/lint_FAA.21549.5vaOfQ
/tmp/lint_GAA.21549.6vaOfQ


% cat match.c
#include <stdio.h>
#include <glob.h>

...
glob_t buf;

if (argc == 1) return 0;

glob(argv[1], 0 , NULL , &buf);

printf("\nNumber of matches found for pattern '%s': %d\n",
      argv[1], buf.gl_pathc);

for (int i = 0; i < buf.gl_pathc; ++i) {
    printf("\n\t%d. %s", (i + 1), buf.gl_pathv[i]);
}

globfree(&buf);
...


% ./<executable> /tmp/lint_\*

Number of matches found for pattern '/tmp/lint_*': 7

        1. /tmp/lint_AAA.21549.0vaOfQ
        2. /tmp/lint_BAA.21549.1vaOfQ
        3. /tmp/lint_CAA.21549.2vaOfQ
        4. /tmp/lint_DAA.21549.3vaOfQ
        5. /tmp/lint_EAA.21549.4vaOfQ
        6. /tmp/lint_FAA.21549.5vaOfQ
        7. /tmp/lint_GAA.21549.6vaOfQ

Please check the man page out for details -- glob(3C).


2. Microtime[stamp]

One of the old blog posts has an example to extract the current timestamp using time API. It shows the timestamp in standard format month-date-year hour:min:sec. In this post, let's add microseconds to the timestamp.

Here is the sample code.

% cat microtime.c
#include <stdio.h>
#include <time.h>

...
char timestamp[80], etimestamp[80];
struct timeval tmval;
struct tm *curtime;

gettimeofday(&tmval, NULL);

curtime = localtime(&tmval.tv_sec);
if (curtime == NULL) return 1;

strftime(timestamp, sizeof(timestamp), "%m-%d-%Y %X.%%06u", curtime);
snprintf(etimestamp, sizeof(etimestamp), timestamp, tmval.tv_usec);

printf("\ncurrent time: %s\n", etimestamp);
...

% ./<executable>
current time: 01-31-2015 15:49:26.041111

% ./<executable>
current time: 01-31-2015 15:49:34.575214

One major change from old approach is the reliance on gettimeofday() since it returns a structure [timeval] with a member variable [tv_usec] to represent the microseconds.

strftime() fills up the date/time data in timestamp variable as per the specifiers used in time format (third argument). By the time strftime() completes execution, timestamp will have month-date-year hr:min:sec filled out. Subsequent snprintf fills up the only remaining piece of time data - microseconds - using the tv_usec member in timeval structure and writes the updated timestamp to a new variable, etimestamp.

Credit: stackoverflow user unwind.


3. Concatenating Multi-Formatted Strings

I have my doubts about this header - so, let me show an example first. The following rudimentary example attempts to construct a sentence that is something like "value of pi = (22/7) = 3.14". In other words, the sentence has a mixture of character strings, integers, floating point number and special characters.

% cat fmt.c
#include <stdio.h>
#include <string.h>

...
char tstr[48];
char pistr[] = "value of pi = ";
int num = 22, den = 7;
float pi = ((float)num/den);

char snum[8], sden[8], spi[8];

sprintf(sden, "%d", den);
sprintf(snum, "%d", num);
sprintf(spi, "%0.2f", pi);

strcpy(tstr, pistr);
strcat(tstr, "(");
strcat(tstr, snum);
strcat(tstr, "/");
strcat(tstr, sden);
strcat(tstr, ") = ");
strcat(tstr, spi);

puts(tstr);
...

% ./<executable>
value of pi = (22/7) = 3.14

Nothing seriously wrong with the above code. It is just that it uses a bunch of sprintf(), strcpy() and strcat() calls to construct the target string. Also it overallocates the memory required for the actual string.

The same effect can be achieved by using asprintf(). The resulting code will be much smaller and easy to maintain however. This function also eases the developer from the burden of allocating memory of appropriate size. In general, overallocation leads to memory wastage and underallocation likely leads to buffer overflows posing unnecessary security risks. When relying on asprintf(), developers are not relieved from two factors though -- checking the return value to see if the call succeeded, and in freeing up the buffer when done with it. Ignoring those two aspects lead to program failures in the worst case, and memory leaks are almost guaranteed.

Here is the alternate version that achieves the desired effect by making use of asprintf().

% cat ifmt.c
#include <stdio.h>
#include <stdlib.h>

...
char *tstr;
int num = 22, den = 7;
float pi = ((float)num/den);

int ret = asprintf(&tstr, "value of pi = (%d/%d) = %0.2f", num, den, pi);

if (ret == -1) return 1;

puts(tstr);
free(tstr);
...

% ./<executable>
value of pi = (22/7) = 3.14

(Full copy of the same blog post with complete examples can be found at:
http://technopark02.blogspot.com/2015/01/programming-in-c-few-tidbits-4.html)

Tuesday Dec 23, 2014

Solaris Studio : C/C++ Dynamic Analysis

First, a reminder - Oracle Solaris Studio 12.4 is now generally available. Check the Solaris Studio 12.4 Data Sheet before downloading the software from Oracle Technology Network.

Dynamic Memory Usage Analysis

Code Analyzer tool in Oracle Solaris Studio compiler suite can analyze static data, dynamic memory access data, and code coverage data collected from binaries that were compiled with the C/C++ compilers in Solaris Studio 12.3 or later. Code Analyzer is supported on Solaris and Oracle Enterprise Linux.

Refer to the static code analysis blog entry for a quick summary of steps involved in performing static analysis. The focus of this blog entry is the dynamic portion of the analysis. In this context, dynamic analysis is the evaluation of an application during runtime for memory related errors. Main objective is to find and debug memory management errors -- robustness and security assurance are nice side effects however limited their extent is.

Code Analyzer relies on another primary Solaris Studio tool, discover, to find runtime errors that are often caused by memory mismanagement. discover looks for potential errors such as accessing outside the bounds of the stack or an array, unallocated memory reads and writes, NULL pointer deferences, memory leaks and double frees. Full list of memory management issues analyzed by Code Analyzer/discover is at: Dynamic Memory Access Issues

discover performs the dynamic analysis by instrumenting the code so that it can keep track of memory operations while the binary is running. During runtime, discover monitors the application's use of memory by interposing on standard memory allocation calls such as malloc(), calloc(), memalign(), valloc() and free(). Fatal memory access errors are detected and reported immediately at the instant the incident occurs, so it is easy to correlate the failure with actual source. This behavior helps in detecting and fixing memory management problems in large applications with ease somewhat. However the effectiveness of this kind of analysis highly depends on the flow of control and data during the execution of target code - hence it is important to test the application with variety of test inputs that may maximize code coverage.

High-level steps in using Code Analyzer for Dynamic Analysis

Given the enhancements and incremental improvements in analytical tools, Solaris Studio 12.4 is recommended for this exercise.

  1. Build the application with debug flags

    –g (C) or -g0 (C++) options generate debug information. It enables Code Analyzer to display source code and line number information for errors and warnings.

    • Linux users: specify –xannotate option on compile/link line in addition to -g and other options
  2. Instrument the binary with discover

    % discover -a -H <filename>.%p.html -o <instrumented_binary> <original_binary>

    where:

    • -a : write the error data to binary-name.analyze/dynamic directory for use by Code Analyzer
    • -H : write the analysis report to <filename>.<pid>.html when the instrumented binary was executed. %p expands to the process id of the application. If you prefer the analysis report in a plain text file, use -w <filename>.%p.txt instead
    • -o : write the instrumented binary to <instrumented_binary>

    Check Command-Line Options page for the full list of discover supported options.

  3. Run the instrumented binary

    .. to collect the dynamic memory access data.

    % ./<instrumented_binary> <args>

  4. Finally examine the analysis report for errors and warnings

Example

The following example demonstrates the above steps using Solaris Studio 12.4 C compiler and discover command-line tool. Same code was used to demonstrate static analysis steps as well.

Few things to be aware of:

  • If the target application preloads one or more functions using LD_PRELOAD environment variable that discover tool need to interpose on for dynamic analysis, the resulting analysis may not be accurate.
  • If the target application uses runtime auditing using LD_AUDIT environment variable, this auditing will conflict with discover tool's use of auditing and may result in undefined behavior.

Reference & Recommended Reading:

  1. Oracle Solaris Studio 12.4 : Code Analyzer User's Guide
  2. Oracle Solaris Studio 12.4 : Discover and Uncover User's Guide

Friday Nov 28, 2014

Solaris Studio 12.4 : C/C++ Static Code Analysis

First things first -- Oracle Solaris Studio 12.4 is now generally available. One of the key features of this release is the support for the latest industry standards including C++11, C11 and OpenMP 4.0. Check the Solaris Studio 12.4 Data Sheet before downloading the software from Oracle Technology Network.

Static Code Analysis

Code Analyzer tool in Oracle Solaris Studio compiler suite can analyze static data, dynamic memory access data, and code coverage data collected from binaries that were compiled with the C/C++ compilers in Solaris Studio 12.3 or later. Code Analyzer is supported on Solaris and Oracle Enterprise Linux.

Primary focus of this blog entry is the static code analysis.

Static code analysis is the process of detecting common programming errors in code during compilation. The static code checking component in Code Analyzer looks for potential errors such as accessing outside the bounds of the array, out of scope variable use, NULL pointer deferences, infinite loops, uninitialized variables, memory leaks and double frees. The following webpage in Solaris Studio 12.4: Code Analyzer User's Guide has the complete list of errors with examples.

    Static Code Issues analyzed by Code Analyzer

High-level steps in using Code Analyzer for Static Code analysis

Given the enhancements and incremental improvements in analysis tools, Solaris Studio 12.4 is recommended for this exercise.

  1. Collect static data

    Compile [all source] and link with –xprevise=yes option.

    • when using Solaris Studio 12.3 compilers, compile with -xanalyze=code option.
    • Linux users: specify –xannotate option on compile/link line in addition to -xprevise=yes|-xanalyze=code.

    During compilation, the C/C++ compiler extracts static errors automatically, and writes the error information to the sub-directory in <binary-name>.analyze directory.

  2. Analyze the static data

    Two options available to analyze and display the errors in a report format.

Example

The following example demonstrates the above steps using Solaris Studio 12.4 C compiler and codean command-line tool.

Few things to be aware of:

  • compilers may not be able to detect all of the static errors in target code especially if the errors are complex.
  • some errors depend on data that is available only at runtime -- perform dynamic analysis as well.
  • some errors are ambiguous, and also might not be actual errors -- expect few false-positives.

Reference & Recommended Reading:
    Oracle Solaris Studio 12.4 Code Analyzer User's Guide

Tuesday Sep 30, 2014

Programming in C: Few Tidbits #3

1) Not able to redirect the stdout output from a C program/application to a file

Possible cause:

Buffered nature of standard output (stdout) stream. Might be waiting for a newline character, for the buffer to be full, or for some other condition to be met based on implementation.

Few potential workarounds:

  • Explicit flushing of standard output stream where needed.
        fflush(stdout);

            -or-

  • Relying on no buffering standard error (stderr) in place of stdout stream.

            -or-

  • Turning-off buffering explicitly by calling setbuf() or setvbuf().
    eg.,
    
    /* just need one of the following two calls, but not both */
    setbuf (stdout, NULL);
    setvbuf(stdout, NULL, _IONBF, 0);  // last argument value need not really be zero
    

2) Printing ("escaping" maybe?) a percent sign (%) in a printf formatted string

Conversion/format specifiers start with a % sign, and using the slash sequence to escape the % sign in strings that are not format specifiers usually does not work. Check the following example out.

eg.,

Executing the following code:

        int pct = 35;
        printf("\n%d%", pct);

.. results in:

35, but not 35% as one would expect.

Format specifier "%%" simply prints the percent sign - so, the desired result can be achieved by replacing "%d%" with "%d%%" in printf statement.

        int pct = 35;
        printf("\n%d%%", pct);

.. shows:

35% as expected

(web search keywords: C printf conversion specification)


3) Duplicating a structure

If the structure has no pointers, assigning one struct to another struct duplicates the structure. The same effect can be achieved by using memcpy() too, but it is not really necessary. After the execution of struct assignment, there will be two copies of struct with no dependency - so, they can be operated independently without impacting the other. The following sample code illustrates this point.

eg., #1
	...
	...

	typedef struct human {
		int accno;
        	int age;
	} person;

	...
	...

	person guy1, guy2;

	guy1.accno = 20202;
	guy1.age = 10;

	guy2 = guy1;

	printf("\nAddress of:\n\t-> guy1: %p. guy2: %p", guy1, guy2);

	printf("\n\nBefore update:\n");
	printf("\naccno of:\n\t-> guy1: %d. guy2: %d", guy1.accno, guy2.accno);
	printf("\nage of:\n\t-> guy1: %d. guy2: %d", guy1.age, guy2.age);

	guy1.age = 15;
	guy2.accno = 30303;

	printf("\n\nAfter update:\n");
	printf("\naccno of:\n\t-> guy1: %d. guy2: %d", guy1.accno, guy2.accno);
	printf("\nage of:\n\t-> guy1: %d. guy2: %d", guy1.age, guy2.age);

	...
	...

Execution outcome:

Address of:
        -> guy1: ffbffc38. guy2: ffbffc30

Before update:

accno of:
        -> guy1: 20202. guy2: 20202
age of:
        -> guy1: 10. guy2: 10

After update:

accno of:
        -> guy1: 20202. guy2: 30303
age of:
        -> guy1: 15. guy2: 10

On the other hand, if the structure has pointer variable(s), duplication of a structure using assignment operator leads to pointer variables in both original and copied structures pointing to the same block of memory - thus creating a dependency that could potentially impact both pointer variables with unintended consequences. The following sample code illustrates this.

eg., #2
	...
	...

	typedef struct human {
        	int *accno;
        	int age;
	} person;

	...
	...

	person guy1, guy2;

	guy1.accno = malloc(sizeof(int));
	*(guy1.accno) = 20202;

	guy1.age = 10;
	guy2 = guy1;
	
	...
	...

	guy1.age = 15;
	*(guy2.accno) = 30303;

	...
	...
Execution outcome:
Address of:
        -> guy1: ffbffb48. guy2: ffbffb40

Before update:

accno of:
        -> guy1: 20202. guy2: 20202
age of:
        -> guy1: 10. guy2: 10

After update:

accno of:
        -> guy1: 30303. guy2: 30303
age of:
        -> guy1: 15. guy2: 10

Few people seem to refer this kind of duplication as shallow copy though not everyone agrees with the terminology.

If the idea is to clone an existing struct variable that has one or more pointer variables, then to work independently on the clone without impacting the struct variable it was cloned from, one has to allocate memory manually for pointer variables and copy data from source structure to the destination. The following sample code illustrates this.

eg., #3
	...
	...

	typedef struct human {
        	int *accno;
        	int age;
	} person;

	...
	...

	person guy1, guy2;

	guy1.accno = malloc(sizeof(int));
	*(guy1.accno) = 20202;

	guy1.age = 10;

	guy2.age = guy1.age;
	guy2.accno = malloc(sizeof(int));
	*(guy2.accno) = *(guy1.accno);

	...
	...

	guy1.age = 15;
	*(guy2.accno) = 30303;

	...
	...

Execution outcome:

Address of:
        -> guy1: ffbffaa8. guy2: ffbffaa0

Before update:

accno of:
        -> guy1: 20202. guy2: 20202
age of:
        -> guy1: 10. guy2: 10

After update:

accno of:
        -> guy1: 20202. guy2: 30303
age of:
        -> guy1: 15. guy2: 10

This style of explicit duplication is referred as deep copy by few people though not everyone agrees with the terminology.

Thursday Jul 31, 2014

Programming in C: Few Tidbits #2

(1) ceil() returns an incorrect value?

ceil() rounds the argument upward to the nearest integer value in floating-point format. For example, calling ceil() with an argument (2/3) should return 1.

printf("\nceil(2/3) = %f", ceil(2/3));

results in:

ceil(2/3) = 0.000000

.. which is not the expected result.

However:

printf("\nceil((float)2/3) = %f", ceil((float)2/3));

shows the expected result.

ceil((float)2/3) = 1.000000

The reason for the incorrect result in the first attempt can be attributed to the integer division. Since both operands in the division operation are integers, it resulted in an integer division which discarded the fractional part.

Desired result can be achieved by casting one of the operands to float or double as shown in the subsequent attempt.

One final example for the sake of completeness.

printf("\nceil(2/(float)3) = %f", ceil(2/(float)2));
..
ceil(2/(float)3) = 1.000000

(2) Main difference between abort() and exit() calls

On a very high level: abort() sends SIGABRT signal causing abnormal termination of the target process without calling functions registered with atexit() handler, and results in a core dump. Some cleanup activity may happen.

exit() causes normal process termination after executing functions registered with the atexit() handler, and after performing cleanup activity such as flushing and closing all open streams.

If it is desirable to bypass atexit() registered routine(s) during a process termination, one way is to call _exit() rather than exit().

Of course, this is all high level and the information provided here is incomplete. Please check relevant man pages for detailed information.


(3) Current timestamp

The following sample code shows the current timestamp in two different formats. Check relevant man pages for more information.

#include <time.h>
..
char timestamp[80];
time_t now;
struct tm *curtime;

now = time(NULL);
curtime = localtime(&now);

strftime(timestamp, sizeof(timestamp), "%m-%d-%Y %X", curtime);

printf("\ncurrent time: %s", timestamp);
printf("\ncurrent time in a different format: %s", asctime(curtime));
..

Executing this code shows output

current time: 07-31-2014 22:05:42
current time in a different format: Thu Jul 31 22:05:42 2014

Monday Jun 30, 2014

Programming in C: Few Tidbits

.. with little commentary aside. Target audience: new programmers. These tips are equally applicable in C and C++ programming environments.


1. Duplicating a file pointer

Steps: find the integer file descriptor associated with the file stream using fileno() call, make a copy of the file descriptor using dup() call, and finally associate the file stream with the duplicated file descriptor by calling fdopen().

eg.,
FILE *fptr = fopen("file", "mode");

FILE *fptrcopy = fdopen( dup( fileno(fptr) ), "mode");

2. Capturing the exit code of a command that was invoked using popen()

Using pipes is one way of executing commands programmatically that are otherwise invoked from a shell. While pipes are useful in performing tasks other than executing shell commands, this tip is mainly about the exit code of a command (to figure out whether it succeeded or failed) that was executed using popen() API.

To capture the exit code, simply use the value returned by pclose(). This function call returns the termination status of the command that was executed as a child process. However the termination status of the child process is in the top 16 bits of the return value, so dividing the pclose() return value by 256 gives the actual exit code of the command that was executed.

eg.,
...
FILE *ptr;
int rc;

if ((ptr = popen("ls", "r")) != NULL) {
	rc = pclose(ptr)/256;
	printf("\nls: exit code = %d", rc);
}

if ((ptr = popen("ls -W", "r")) != NULL) {
	rc = pclose(ptr)/256;
	printf("\nls -W: exit code = %d", rc);
}
...

% ./<executable>

ls: exit code = 0
ls: illegal option -- W
ls -W: exit code = 2

3. Converting an integer to a string

Standard C library has implementation for converting a string to an integer (atoi()), but not for converting an integer to a string. One way to achieve the desired result is by using sprintf() function call, which writes formatted data to a string.

eg.,
int weight = 30;
char *wtstr = malloc(sizeof(char) * 3);

sprintf(wtstr, "%d", weight);
...

sprintf() can also be used to convert data in other data types such as float, double to string. Also see: man page for snprintf().


4. Finding the length of a statically allocated array

When size was not specified explicitly, simply divide the total size of the array by the size of the first array element.

eg.,
static const char *greeting[] = { "Hi", "Hello", "Hola", "Bonjour", \
                                    "Namaste", "Ciao", "Ni Hao" };
int numgreetings = sizeof(greeting)/sizeof(greeting[0]);

After execution, numgreetings variable holds a value of 7. Note that sizeof(greeting[0]) is actually the size of a pointer to a character array.

  • sizeof is not a function, but an operator -- hence it is not necessary or required to use parentheses when using it.
  • Though not so useful, this is applicable even when the size was explicitly specified.

Wednesday Dec 15, 2010

Oracle Solaris Studio C/C++ : Inline Functions

Function inlining improves runtime performance by replacing a call to a function with the body of the function. This eliminates the overhead of jumping to and returning from a subroutine. An additional advantage is that placing the function code "inline" exposes it to further optimization.

The C++ compiler performs two kinds of inlining: front-end (parser) and back-end (code generator). The C and Fortran compilers support only back-end inlining. The same code generator is used for all compilers on a platform. The C++ compiler performs front-end inlining because it can use its knowledge of C++ semantics to eliminate extra copies of objects among other things that the code generator would not be able to do. The back-end inlining does not depend on the programming language.

The C++ compiler front end attempts to inline a function declared inline. If the function is too large, a warning message will be printed on stdout when +w or +w2 ("more warnings") option is used. The +d option prevents the front end from attempting to inline any function. The -g option also turns off front-end inlining. The -O options do not affect front-end inlining. C++ front end turns off function inlining when a combination of -g and -O options are specified on compile line. It may lead to loss of runtime performance. To avoid this, it is suggested to use -g0 instead of -g. C does not have -g0 so use -g instead.

With an optimization level of -O4 or higher, the code generator examines all functions independent of how they were declared in source code and replace function calls with inline code where it thinks the replacement will be beneficial. No diagnostic messages are displayed about back-end inlining. The +d option has no impact on back-end inlining.

Couple of trivial examples to demonstrate the compiler behavior.

eg.,

% cat inline.c

#include <stdio.h>

inline void printmespam() {
        printf("print me"); printf("print me"); 
        printf("print me"); printf("print me");
        printf("print me"); printf("print me"); 
        printf("print me"); printf("print me");
        printf("print me"); printf("print me"); 
        printf("print me"); printf("print me");
        printf("print me"); printf("print me"); 
        printf("print me"); printf("print me");
        printf("print me");
}

inline void printme() {
        printf("print me");
}

int main() {
        printme();
        printmespam();
        return (0);
}

% CC +w2 inline.c
"inline.c", line 17: Warning: "printmespam()" is too large and will not be expanded inline.
1 Warning(s) detected.

In the above example, printmespam() was not inlined by the compiler though it was explicitly requested to do so. The keyword inline is only a request but not a guarantee.

How to check if a routine is inlined?

A: Check the symbol table of the executable. If the routine doesn't show up in the symbol table, it is an indication that the missing routine is inlined. This is because the compiler might have replaced the function call with the body of the function.

% elfdump -CsN.symtab a.out | grep printme
      [85]  0x00010e68 0x000000a4  FUNC GLOB  D    0 .text       void printmespam()

printme is inlined where as printmespam is not.

Another way is to check the assembly code being generated. To generate the assembly code, compile the code with -S option of Oracle Solaris Studio compilers.

eg.,

% cat swap.c

void swap (int \*a, int \*b) {
        int t = \*a;
        \*a = \*b;
        \*b = t;
}

int main (int argc, char \*argv) {
        int x = 5, y = 2;
        swap (&x,&y);
        return (0);
}

% CC +w2 -S swap.c

% grep call swap.s
        call    __1cEswap6Fpi0_v_

% dem __1cEswap6Fpi0_v_
__1cEswap6Fpi0_v_ == void swap(int\*,int\*)

From the above output(s), it is clear that the function is not inlined since an assembly instruction has been generated with a call to routine swap. Let's add the keyword inline to the function definition.

% cat swap.c

inline void swap (int \*a, int \*b) {
        int t = \*a;
        \*a = \*b;
        \*b = t;
}

int main (..) { .. }

% CC +w2 -S swap.c
% grep call swap.s
%

After instructing the compiler to inline the routine swap, the compiler was able to inline the function in main() mainly because it was not too big. That is why no assembly instruction has been generated with a call to swap.

Another example to demonstrate slightly different behavior.

eg.,

% cat inline2.c

#include <stdio.h>

int globvar = 0;

inline void setglob () {
        globvar= 25;
}

int main (int argc, char \*argv[]) {
        globvar= 5;
        setglob ();
        printf ("Now global variable holds %d\\n", globvar);
        return (0);
}

% cc -o test inline2.c
Undefined                       first referenced
 symbol                             in file
setglob                             inline2.o
ld: fatal: Symbol referencing errors. No output written to test

The above code violates a C rule. An inline definition without an extern directive does not create an instance of the function. Calling the function has undefined results. The fix is to declare setglob with external linkage as shown below.

C++ has a different rule for inline functions. The compiler is required to figure out how to generate a defining instance if one is needed without any special action by the programmer. So the above example has valid C++ code but it is valid in C.

 
% cat inline2.c

#include <stdio.h>

int globvar = 0;

extern inline void setglob () {
        globvar= 25;
}

int main (..) { .. }

% cc inline2.c
% ./a.out
Now global variable holds 25

Notes:

  1. Do not use if(0) in an inline function. Use #if 0 instead

  2. Do not put a return statement in the "then" part of an "if" statement. Rearrange the code to put the return in the "else" part or outside the if-else entirely.

Acknowledgements:
Steve Clamage

(Location of original blogpost:
http://technopark02.blogspot.com/2005/04/sun-cc-compilers-inlining-routines.html)

Sunday Apr 15, 2007

C/C++: Printing Stack Trace with printstack() on Solaris

On Solaris 9 and later, libc provides an useful function called printstack, to print a symbolic stack trace to the specified file descriptor. This is useful for reporting errors from an application during run-time.

If the stack trace appears corrupted, or if the stack cannot be read, printstack() returns -1.

Here is a programmatic example:

% more printstack.c

#include <stdio.h>
#include <ucontext.h>

int callee (int file)
{
    printstack(file);
    return (0);
}

int caller()
{
    int a;
    a = callee (fileno(stdout));
    return (a);
}

int main()
{
    caller();
    return (0);
}

% cc -V
cc: Sun C 5.8 Patch 121016-04 2006/10/18 <- Sun Studio 12 C compiler
usage: cc [ options] files.  Use 'cc -flags' for details

% cc -o stacktrace stacktrace.c

% ./stacktrace
/tmp/stacktrace:callee+0x18
/tmp/stacktrace:caller+0x22
/tmp/stacktrace:main+0x14
/tmp/stacktrace:0x6d2

The printstack() function uses dladdr1() to obtain symbolic symbol names. As a result, only global symbols are reported as symbol names by printstack().

The stack trace from a C++ program, will have all the symbols in their mangled form. So as of now, the programmers may need to have their own wrapper functions to print the stack trace in demangled form.

% CC -V
CC: Sun C++ 5.8 Patch 121018-07 2006/11/01 <- Sun Studio 12 C++ compiler

% CC -o stacktrace stacktrace.c

% ./stacktrace
/tmp/stacktrace:__1cGcallee6Fi_i_+0x18
/tmp/stacktrace:__1cGcaller6F_i_+0x22
/tmp/stacktrace:main+0x14
/tmp/stacktrace:0x91a

There has been an RFE (Request For Enhancement), 6182270 printstack should provide demangled names while using with c++, in place against Solaris' libc to print the stack trace in demangled form, when printstack() has been called from a C++ program. This will be released as a libc patch for Solaris 9 & 10, as soon as it gets enough attention.

% /usr/ccs/bin/elfdump libc.so.1  | grep printstack
     [677]  0x00069f70 0x0000004c  FUNC WEAK  D    9 .text          printstack
    [1425]  0x00069f70 0x0000004c  FUNC GLOB  D   36 .text          _printstack
     ...

Since the object code is automatically linked with libc during the creation of an executable or a dynamic library, the programmer need not specify -lc on the compile line.

Suggested Reading:
Man page of walkcontext / printstack

(Original blog post is at:
http://technopark02.blogspot.com/2005/05/cc-printing-stack-trace-with.html)

About

Benchmark announcements, HOW-TOs, Tips and Troubleshooting

Search

Archives
« February 2015
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
       
       
Today