K
Kevin Bracey
Having just upgraded a compiler, I found that the Internet stack in one of
our systems no longer functioned - it appeared that the compiler was not
compiling it correctly.
Closer inspection showed that it was actually a really nasty example of
undefined behaviour being invoked.
#define ETHER_ADDR_LEN 6
struct ether_header {
u_char ether_dhost[ETHER_ADDR_LEN];
u_char ether_shost[ETHER_ADDR_LEN];
u_short ether_type;
};
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
{
u_char edst[ETHER_ADDR_LEN];
struct sockaddr *dst;
struct ether_header *eh;
//...
eh = (struct ether_header *)dst->sa_data;
memcpy(edst, eh->ether_dhost, sizeof (edst));
//...
}
This always used to work, despite there being an alignment problem - all
structures on this platform are word (32-bit) aligned, so eh is
incorrectly aligned, invoking undefined behaviour.
Looking at it, it's not clear how it could actually go wrong - you'd
instinctively assume that memcpy will work regardless of the alignment.
But the new compiler recognised memcpy, and decided it might like to inline
it. Further, it knew that the destination was a local array, and thus was
word-aligned on the stack with two bytes of padding after it. It further
"knew" that the source was at the start of a structure, and hence was
word-aligned. It thus decided that the most efficient code to output was an
in-line two-word (8-byte) copy from eh to edst. But eh was not actually
word-aligned, so the load-word instructions failed (silently reading the
wrong data).
The whole thing just goes to show that with today's modern optimising
compilers, you never know when undefined behaviour might bite you. Even
things that instinctively "should work anyway" can go horribly wrong.
our systems no longer functioned - it appeared that the compiler was not
compiling it correctly.
Closer inspection showed that it was actually a really nasty example of
undefined behaviour being invoked.
#define ETHER_ADDR_LEN 6
struct ether_header {
u_char ether_dhost[ETHER_ADDR_LEN];
u_char ether_shost[ETHER_ADDR_LEN];
u_short ether_type;
};
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
{
u_char edst[ETHER_ADDR_LEN];
struct sockaddr *dst;
struct ether_header *eh;
//...
eh = (struct ether_header *)dst->sa_data;
memcpy(edst, eh->ether_dhost, sizeof (edst));
//...
}
This always used to work, despite there being an alignment problem - all
structures on this platform are word (32-bit) aligned, so eh is
incorrectly aligned, invoking undefined behaviour.
Looking at it, it's not clear how it could actually go wrong - you'd
instinctively assume that memcpy will work regardless of the alignment.
But the new compiler recognised memcpy, and decided it might like to inline
it. Further, it knew that the destination was a local array, and thus was
word-aligned on the stack with two bytes of padding after it. It further
"knew" that the source was at the start of a structure, and hence was
word-aligned. It thus decided that the most efficient code to output was an
in-line two-word (8-byte) copy from eh to edst. But eh was not actually
word-aligned, so the load-word instructions failed (silently reading the
wrong data).
The whole thing just goes to show that with today's modern optimising
compilers, you never know when undefined behaviour might bite you. Even
things that instinctively "should work anyway" can go horribly wrong.