Oh gcc…

Yes, I know about sequence points.. and I know we’re asking for trouble, and wantonly ignoring warnings of undefined behavior when using expressions with multiple side-effects to the same variable, but really.. a little consistency would be nice.

Take this code for example:

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
29
30
31
32
#include <stdio.h>
 
void *i;
volatile void *j;
 
int main()
{
    void *k;
    volatile void *l;
    i = k = (void*)10;
    j = l = (void*)10;
    printf("%d\n", (++i) == (++i));
    printf("%d\n", (++j) == (++j));
    printf("%d\n", (++k) == (++k));
    printf("%d\n", (++l) == (++l));
    printf("%p %p %p %p\n", i, j, k, l); 
    i = k = (void*)10;
    j = l = (void*)10;
    printf("%d\n", (i++) == (i++));
    printf("%d\n", (j++) == (j++));
    printf("%d\n", (k++) == (k++));
    printf("%d\n", (l++) == (l++));
    printf("%p %p %p %p\n", i, j, k, l);
    i = k = (void*)10;
    j = l = (void*)10;
    printf("%d\n", (i++) && (i++));
    printf("%d\n", (j++) && (j++));
    printf("%d\n", (k++) && (k++));
    printf("%d\n", (l++) && (l++));
    printf("%p %p %p %p\n", i, j, k, l);
    return 0;
}

Can you predict what it’ll print? Here are my results with gcc-4.3.3.

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
29
30
31
32
33
34
35
amandla> gcc test.c -o noopt
amandla> gcc -O test.c -o opt
amandla> ./noopt 
0
0
1
1
0xc 0xc 0xc 0xc
1
1
1
1
0xb 0xb 0xc 0xc
1
1
1
1
0xc 0xc 0xc 0xc
amandla> ./opt 
1
1
1
1
0xc 0xc 0xc 0xc
1
1
1
1
0xb 0xb 0xc 0xc
1
1
1
1
0xc 0xc 0xc 0xc
amandla>

Note how the first 2 values (results of comparisons) change when optimizations are applied and how the second set of printed pointers stubbornly contains some 0xb entries. Granted, if I add “-Wsequence-point” to my compile flags then gcc will suggest that my variables may be undefined state at a few places.

Interestingly, Intel’s compiler gives me something more in line with what I’d expect.. and bitches about my rampant abuse of the language without my needed to turn on any warning flags. With or without optimizations, the results are:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
amandla> ./icctest 
0
0
0
0
0xc 0xc 0xc 0xc
1
1
1
1
0xc 0xc 0xc 0xc
1
1
1
1
0xc 0xc 0xc 0xc
amandla>

I suppose I should be happy that gcc did not openly mocked the code in iambic pentameter or simply refused to compile it out of principle.

Leave a Comment

Sometimes its easier to just write the code

Here’s a straightforward way to daemonize some primitive app. It supports redirecting stderr and stdout. It close stdin and writes a pid to a pidfile.

Use would look like this:

1
2
3
4
amandla> ./daemonize -e /tmp/stderr -o /tmp/stdout \ 
 -p /tmp/pidfile /opt/bin/crapapp 
amandla> cat /tmp/pidfile
32139

Where 32139 is the PID of crapapp and files in /tmp contain what you’d expect. There’s probably some existing way to do this easily but sometimes just writing the code is quicker and easier than uhh googling it. Deliver yesterday, program today, think tomorrow. Yeah, that’s how we do it. Here’s the code..

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h> 
 
int open_or_die(char *path, int flags) {
    int file;
    if ((NULL == path) || (-1 == (file = open(path, flags)))) {
        (void)fprintf (stderr, "Failed to open file \"%s\": %s\n", 
            path, strerror(errno));
        exit(EXIT_FAILURE);
    }
    return file;
}
 
int main(int argc, char *argv[]) {     
    // Check for some args.
    if (8 > argc) {
        (void) puts(
            "Usage: daemonize options path [args]\n" 
            "\t-i <filename> Redirect stdin from file <filename>\n"
            "\t-e <filename>Redirect stderr to file <filename>\n" 
            "\t-o <filename>Redirect stdout to file <filename>\n" 
            "\t-p <filename>Write pid to <filename>\n");
        exit (EXIT_FAILURE);
    }
 
    // Parse args.
    int option;
    char **command = NULL, *pid_filename, *stdin_filename, 
        *stdout_filename, *stderr_filename = NULL;
    while (-1 != (option = getopt(argc, argv, "+i:e:o:p:"))) {
        switch (option) {
        case 'i': stdin_filename = optarg; break;
        case 'e': stderr_filename = optarg; break;
        case 'o': stdout_filename = optarg; break;
        case 'p': pid_filename = optarg; break;
        default: (void) fprintf(stderr, 
            "Unknown option: -%c\n", optopt);
        }
    }
 
    // Assume the command to daemonize is 
    // the rest of the arguments
    command = &argv[optind];        
 
    // Make a token attempt to see if we'll be able 
    // to exec the command.
    if (-1 == access (command[0], F_OK)) {
        (void) fprintf(stderr, "Can't access %s, exiting.", 
            command[0]);
        exit(EXIT_FAILURE);
    }
 
    // Try to open some files for pid, 
    // stdin, stdout, stderr.
    FILE *pid_file = fopen(pid_filename, "w+");
    int stdin_file = open_or_die(stdin_filename, 
        O_NONBLOCK | O_RDWR);
    int stderr_file = open_or_die(stderr_filename, 
        O_NONBLOCK | O_RDWR);
    int stdout_file = open_or_die(stdout_filename, 
        O_NONBLOCK | O_RDWR);
 
    // Redirect stdin, stderr, stdout.
    close(STDIN_FILENO);
    dup2(stdin_file, STDIN_FILENO);
    close(STDOUT_FILENO);
    dup2(stdout_file, STDOUT_FILENO);
    close(STDERR_FILENO);
    dup2(stderr_file, STDERR_FILENO);
 
    // Now daemonize..
    if (0 != daemon (0, 1))  {
        (void) fprintf (stderr, 
            "Can't daemonize: %s\nExiting.", 
            strerror(errno));
        exit(EXIT_FAILURE);
    }
 
    // Write the pid
    fprintf(pid_file, "%d\n", getpid ());
    fclose(pid_file);
 
    // And away we go..
    execvp(command[0], command);
}

Leave a Comment

Stacktraces for c++

I must be getting old. I felt the need for stacktraces in some c++ code I’m running that ehhh occasionally has trouble dealing with life and decides to blow up rather ignominiously in depths of some 3rd party library. This is entirely un-portable and frankly, my dear, I don’t have a ham. Linux and g++. Deal with it. If it works anywhere else, its a miracle and you can send me a check. If it doesn’t work, you get to keep the pieces.

So, roughly speaking, when our test program blows up we want to see it dump a somewhat readable stacktrace with unmangled (demangled?!) c++ names.. not the blah blah that looks random junk but is in fact encoded facts about the name.. like so:

1
2
3
4
5
6
7
8
9
10
amandla> ./test 
stacktrace_exception::stacktrace_exception()
three()
foo::call_three()
two()
one()
main()
__libc_start_main()
./test [0x401619]
amandla>

where this is the test code:

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
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <cstdlib>
#include <stacktrace_exception.hpp>
 
void three() throw(stacktrace_exception)
{
        stacktrace_exception e;
        throw e;
}
 
struct foo 
{
        int call_three() 
        {
                three();
        }
};
 
void two() throw(stacktrace_exception)
{
        foo f;  
        f.call_three();
}
 
void one() throw(stacktrace_exception)
{
        two();
}
 
int main()
{
        using namespace std;
        try {
                one();
        }
        catch (stacktrace_exception& e) {
                cout << e.what() << endl;
        }
        return EXIT_SUCCESS;
}

On my Ubuntu system, there are some rather useful functions in /usr/include/c++/4.3/cxxabi.h that deal with demangling names. You have to love code that contains “magic placehoder classes”. But, I digress. Without further ado, here is the header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef _STACKTRACE_EXCEPTION_HPP
#define _STACKTRACE_EXCEPTION_HPP
 
#include <string>
#include <exception>
#include <execinfo.h>
#include <cxxabi.h>
 
static const int max_stacktrace_depth = 256;
 
class stacktrace_exception : public std::exception {
public:
        explicit stacktrace_exception();
        ~stacktrace_exception() throw();
        virtual const char* what() const throw();
 
private:
        std::string trace;
};
 
#endif

And the implementation.. with some comments to describe the more shameful aspects..

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <cstdlib>
#include <cstring>
#include <stacktrace_exception.hpp>
 
stacktrace_exception::stacktrace_exception() 
{
    // Unfortunately we have to commit to a 
    // maximum stack trace depth. *sigh*
    void *buffer[max_stacktrace_depth];
 
    // Walk the stack and fill in a backtrace 
    // in our buffer.
    int number_of_addresses 
        = backtrace(buffer, max_stacktrace_depth);
 
    // Get the symbols for the stack trace.
    char **strings 
        = backtrace_symbols(buffer, number_of_addresses);
 
    // Since we're in c++ we should attempt to 
    // demangle the entries.
    for (int i = 0; i < number_of_addresses; ++i) {
	// Take a guess at the maximum width of a 
        // name - template names can be wide. 
	size_t max_name_width = 1024;
 
	// Malloc up some space; we use the C functions 
        // because of the code we're interfacing with. 
	char *demangled_name 
            = static_cast<char*>(malloc(max_name_width));
 
	// Begin and end of the mangled name.
	char *begin = 0, *end = 0;
 
        // Find the the parentheses and address offset 
        // surrounding the name.
	for (char *j = strings[i]; *j; ++j) {
	    if (*j == '(')
		begin = j;
	    else if (*j == '+') 
		end = j;
	}
 
	// If we found it one..
	if (0 != begin && 0 != end) {
	    // Isolate the name.
	    *begin++ = '\0'; *end = '\0';
 
	    int ignore;
	    char *returned_demangled_name 
            = abi::__cxa_demangle(begin, demangled_name, 
                                 &max_name_width, &ignore);
	    if (0 != returned_demangled_name){
		// We need to do this in case 
                // things got realloc()'d on us.
		demangled_name = returned_demangled_name;
	    }
	    else {
		// Demangling failed, just do our best 
                // with the existing name.
		std::strncpy(demangled_name, begin, max_name_width);
		std::strncat(demangled_name, "()", max_name_width);
		demangled_name[max_name_width-1] = '\0';
	    }
	    trace.append(demangled_name);
	}
	else
	    trace.append(strings[i]);
	trace.append("\n"); 
	free(demangled_name);
    }
    free(strings);
}
 
stacktrace_exception::~stacktrace_exception() throw ()
{
}
 
const char* stacktrace_exception::what() const throw()
{
    return trace.c_str();   
}

Now the astute reader will be cringing at the memory being allocated. We could be in a situation where the glorious edifice of our program is crumbling around our ears due to memory being exhausted, and we should dump the backtrace symbols to stderr via a call to backtrace_symbols_fd(). Such an extension is left as an exercise to the reader.

I’ve long maintained that c++ is a language for consenting adults; any time its used you know someone is going to get fucked. However, its hard to ignore, its generally useful and there are some fine libraries that are best dealt with from their native language. Such is life. Even if it does take “worse is better” to new and dizzying depths.

Leave a Comment

Hello, World!

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
        (void) printf("Hello, World!\n");
        return EXIT_SUCCESS;
}

Leave a Comment