A gcc regression

Recently I stumbled over a gcc problem, I am tempted to call it a bug. Infrequently I compile programs and upload them to a web server to which I only have ftp access. This always worked, but not any longer. Why not? Unfortunately the server reacts to any problem by showing its home page - like with the old "?" in ed, one knows that something is wrong, and may figure out the rest. Small programs worked, big programs failed, and after a while I had a minimal failing program.
#include <string.h>

char *qq, *aa;

int main(int argc, char **argv) {
        char *q = qq;
        int n;

        n = strlen(aa);
        strcpy(q, aa);
        q += n;
        *q = 0;
        return 0;
}
(Irrelevant parts removed.) What is wrong? Why does the web server react with the "page not found" reaction of showing its home page? The binary was executable, permissions OK.
% readelf -a test
...
Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x0000000000400390  Offset: 0x000390  Link: 6 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 2
  0x0010:   Name: GLIBC_2.14  Flags: none  Version: 3
  0x0020:   Name: GLIBC_2.2.5  Flags: none  Version: 2
...
Hmm. Why does it need GLIBC_2.14? That is a rather recent library. And indeed, this turns out to be the culprit. The dynamic loader fails, and programs that refer to GLIBC_2.14 are non-executable on this web server.

memcpy@GLIBC_2.14

Why is such a recent library needed for such a simple program?
% readelf -a test | grep 2.14
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (3)
    59: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@@GLIBC_2.14
  004:   3 (GLIBC_2.14) 
  0x0010:   Name: GLIBC_2.14  Flags: none  Version: 3
Interesting. So my program does not call the function memcpy, but it does call strlen and strcpy and the compiler decides that it would be better to use memcpy, and forces a memcpy from a recent glibc. That means that the compiler output cannot be used on older machines. Bad. Smells like a gcc bug.

The memcpy vs. memmove saga

Now that I know what is wrong I can easily find posts from hundreds (maybe even thousands) of people who had the same problem. People advise workarounds like
__asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
and that works. One can also compile on an older machine, or compile a private version of the toolchain. But such nonsense should not be necessary for very simple programs that have worked for dozens of years. There is a blog discussion fun with shared libraries — version GLIBC_2.14 not found, and an old article in lwn.

The original discussion was about the difference between memcpy and memmove, and that in some implementations the former can only be used on non-overlapping areas. This leads to bugs such a this one in mksquashfs, and this one (with comments from Linus) in the 64bit flash plugin.

That old working programs were suddenly broken was because of the patch by HJLu that was supposed to gain a few ns by copying backwards, but ended up costing thousands of developer hours. Linus tried to measure whether there was a speedup and did not find any.

Andreas Schwab (reacting as if flashplayer were the only program broken by this glibc change) comments: "Tell adobe" and "The only stupidity is crap software violating well known rules that have existed forever". Linus disagrees, gave an LD_PRELOAD workaround, and says "I'd personally suggest that glibc just alias memcpy() to memmove()."

So that was the old saga. HJLu changed glibc. Technically it was doubtful whether the change was a good idea. Maybe there was no speedup. Many old programs broke, a lot of Linux developer time was spent in figuring out why, and there was a lot of discussion. Of course technically such a change is OK, at least if there actually is a speedup and only undefined behavior is broken. See also this discussion. The result was that memcpy@GLIBC_2.2.5 was aliased to memmove (so old binaries remain happy), and memcpy resolves to memcpy@@GLIBC_2.14.

gcc

And so it happened that the memcpy's that gcc generates (for struct copies and the like) became memcpy@@GLIBC_2.14. Unfortunate. Also this broke many projects. Not because of an incorrect call to some function, but because of an added dependency, where the development machine was recent, but the places where the compiled code is going to be deployed have an older glibc. Hundreds of software projects today contain the mysterious line
__asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
as a lasting memory of this glibc debacle. I just added it to my code as well.