Inflation of vmlinux by linker on x86_64

From: Joris van Rantwijk
Date: Fri Sep 26 2008 - 08:58:50 EST



I noticed that a lot of space in the uncompressed vmlinux image is
taken up by alignment issues, at least on x86_64. For example, the
linker decides to align the first section on a 2 MB file offset,
thereby inserting almost 2 MB of zeroes at the beginning of vmlinux.

Idx Name Size VMA LMA File off Algn
0 .text 001d9203 ffffffff80200000 0000000000200000 00200000 2**12
CONTENTS, ALLOC, LOAD, READONLY, CODE

It seems this has gotten worse with recent versions of binutils;
ld 2.18 makes my vmlinux 2 MB larger than ld 2.17.

This inflation of vmlinux causes problems with certain boot loader
configurations, where decompression of the huge vmlinux may overwrite
part of an initramfs image.
See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=479101

Although alignment of memory addresses is required, alignment of
file offsets in vmlinux seems completely unnecessary. After all,
vmlinux is not a demand paged binary. I tried to avoid the alignment
by adding "-n" to the linker command line. This resulted in a 4 MB
reduction of vmlinux, and an immediate crash on boot.

The crash is caused by the ELF loader in x86/boot/compressed/misc.c:
parts of the kernel are memcpy-ed over themselves. This was previously
not an issue because the inflation of vmlinux provided so much slack
that all memcpy-s would turn out in the right direction.

After fixing the ELF loader, it now seems to work quite well.
vmlinux is 4 MB smaller, bzImage is 10 kB smaller and my problems
with LILO are solved.

Would it be appropriate to put this in the kernel?
Note that I am not at all sure that "ld -n" works on all architectures;
I only tested this on i386 and x86_64.

Greetings, Joris.

diff -urN linux-2.6.27-rc7/Makefile linux-2.6.27-rc7j/Makefile
--- linux-2.6.27-rc7/Makefile 2008-09-22 00:29:55.000000000 +0200
+++ linux-2.6.27-rc7j/Makefile 2008-09-26 12:21:34.000000000 +0200
@@ -577,7 +577,7 @@
LDFLAGS_BUILD_ID = $(patsubst -Wl$(comma)%,%,\
$(call ld-option, -Wl$(comma)--build-id,))
LDFLAGS_MODULE += $(LDFLAGS_BUILD_ID)
-LDFLAGS_vmlinux += $(LDFLAGS_BUILD_ID)
+LDFLAGS_vmlinux += $(LDFLAGS_BUILD_ID) -n

# Default kernel image to build when no specific target is given.
# KBUILD_IMAGE may be overruled on the command line or
diff -urN linux-2.6.27-rc7/arch/x86/boot/compressed/misc.c linux-2.6.27-rc7j/arch/x86/boot/compressed/misc.c
--- linux-2.6.27-rc7/arch/x86/boot/compressed/misc.c 2008-09-22 00:29:55.000000000 +0200
+++ linux-2.6.27-rc7j/arch/x86/boot/compressed/misc.c 2008-09-26 13:20:35.000000000 +0200
@@ -281,13 +281,18 @@
return s;
}

+/* This memcpy can handle overlapping dest and src, like memmove. */
static void *memcpy(void *dest, const void *src, unsigned n)
{
int i;
const char *s = src;
char *d = dest;

- for (i = 0; i < n; i++) d[i] = s[i];
+ if (dest <= src) {
+ for (i = 0; i < n; i++) d[i] = s[i];
+ } else {
+ for (i = n - 1; i >= 0; i--) d[i] = s[i];
+ }
return dest;
}

@@ -343,8 +348,8 @@
Elf32_Ehdr ehdr;
Elf32_Phdr *phdrs, *phdr;
#endif
- void *dest;
- int i;
+ void *dest, *src;
+ int i, direction;

memcpy(&ehdr, output, sizeof(ehdr));
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
@@ -364,7 +369,17 @@

memcpy(phdrs, output + ehdr.e_phoff, sizeof(*phdrs) * ehdr.e_phnum);

- for (i = 0; i < ehdr.e_phnum; i++) {
+ /* Go two times through the list of program headers;
+ * first forward, then backward.
+ */
+ for (i = 0, direction = 1; i >= 0; i += direction) {
+
+ if (i >= ehdr.e_phnum) {
+ /* reached the end; turn back */
+ direction = -1;
+ continue;
+ }
+
phdr = &phdrs[i];

switch (phdr->p_type) {
@@ -375,9 +390,17 @@
#else
dest = (void *)(phdr->p_paddr);
#endif
- memcpy(dest,
- output + phdr->p_offset,
- phdr->p_filesz);
+ src = output + phdr->p_offset;
+
+ /* order the copying to avoid overwriting */
+ if ((direction == 1 && dest <= src) ||
+ (direction == -1 && dest > src)) {
+ /* note: unlike standard memcpy, this one
+ * is safe to use when dest and src overlap.
+ */
+ memcpy(dest, src, phdr->p_filesz);
+ }
+
break;
default: /* Ignore other PT_* */ break;
}
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/