Support generating Multiboot-compliant ELF images
authorH. Peter Anvin <hpa@zytor.com>
Tue, 27 May 2008 19:13:24 +0000 (12:13 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Tue, 27 May 2008 19:17:33 +0000 (12:17 -0700)
Support generating Multiboot-compliant ELF images.  This is handled by
generating a Multiboot header inside a dedicated no-SHF_ALLOC section,
which is forced to be the first section in the file.

Clean up the ELF code somewhat, and make it handle no-ALLOC sections
correctly (no PHDR for a section which isn't ALLOC.)

elf.c
main.c
multiboot.h [new file with mode: 0644]
segment.h
wraplinux.1

diff --git a/elf.c b/elf.c
index 040d68e..b420fed 100644 (file)
--- a/elf.c
+++ b/elf.c
 #include <stdlib.h>
 #include <string.h>
 #include <inttypes.h>
+#include <stdbool.h>
 #include "elf32.h"
 #include "segment.h"
+#include "multiboot.h"
 #include "le.h"
 
-static inline uint32_t sh_to_p_type(uint32_t sh_type)
+static inline uint32_t p_type(const struct segment *s)
 {
-       switch (sh_type) {
+       switch (s->sh_type) {
        case SHT_PROGBITS:
        case SHT_NOBITS:
-               return PT_LOAD;
+               return (s->sh_flags & SHF_ALLOC) ? PT_LOAD : PT_NULL;
        case SHT_NOTE:
                return PT_NOTE;
        default:
@@ -40,6 +42,34 @@ static inline uint32_t sh_to_p_type(uint32_t sh_type)
        }
 }
 
+/*
+ * Return true if we should use a single PHDR for s and s->next
+ */
+static bool merge_phdrs(const struct segment *s)
+{
+       const struct segment *n = s->next;
+
+       if (!n)
+               return false;
+
+       if (p_type(s) != PT_LOAD || p_type(n) != PT_LOAD)
+               return false;
+
+       if (s->sh_type != n->sh_type)
+               return false;
+
+       if (s->sh_flags != n->sh_flags)
+               return false;
+
+       if (s->align < n->align)
+               return false;
+
+       if (n->address != align_up(s->address+s->length, n->align))
+               return false;
+
+       return true;
+}
+
 static int gen_elf(struct segment *segs, Elf32_Ehdr *ehdr, FILE *out)
 {
        Elf32_Shdr shdr;
@@ -58,27 +88,22 @@ static int gen_elf(struct segment *segs, Elf32_Ehdr *ehdr, FILE *out)
 
        for (s = segs; s; s = s->next) {
                uint32_t memsize;
-               uint32_t p_type = sh_to_p_type(s->sh_type);
                addr_t align = (addr_t)1 << s->align;
                addr_t address, pad;
 
+               if (p_type(s) == PT_NULL)
+                       continue;
+
                npheader++;
 
                address = s->address;
                memsize = s->length;
 
-               while (p_type == PT_LOAD &&
-                      (s->sh_flags & SHF_ALLOC) &&
-                      s->next &&
-                      s->next->sh_type == s->sh_type &&
-                      s->next->sh_flags == s->sh_flags &&
-                      s->next->align <= s->align &&
-                      (s->next->address ==
-                       align_up(address+memsize, s->next->align))) {
+               while (merge_phdrs(s)) {
                        s = s->next;
                        align = UINT32_C(1) << s->align;
                        pad = (s->address - (address+memsize)) & (align-1);
-                       memsize += s->length + pad;
+                       memsize += pad + s->length;
                }
        }
 
@@ -96,7 +121,7 @@ static int gen_elf(struct segment *segs, Elf32_Ehdr *ehdr, FILE *out)
        data_offset = data_start;
        for (s = segs; s; s = s->next) {
                uint32_t filesize, memsize;
-               uint32_t p_type = sh_to_p_type(s->sh_type);
+               uint32_t ptype = p_type(s);
                uint32_t p_flags;
                uint32_t align = UINT32_C(1) << s->align;
                addr_t address, offset, pad;
@@ -104,52 +129,52 @@ static int gen_elf(struct segment *segs, Elf32_Ehdr *ehdr, FILE *out)
                filesize = (s->sh_type == SHT_NOBITS) ? 0 : s->length;
                memsize = (s->sh_flags & SHF_ALLOC) ? s->length : 0;
 
-               if (s->sh_type != SHT_NOBITS)
-                       data_offset += (s->address - data_offset) & (align-1);
-
-               p_flags = 0;
-               if (s->sh_flags & SHF_ALLOC) /* Somewhat cheesy... */
-                       p_flags |= PF_R;
-               if (s->sh_flags & SHF_EXECINSTR)
-                       p_flags |= PF_X;
-               if (s->sh_flags & SHF_WRITE)
-                       p_flags |= PF_W;
-
-               memset(&phdr, 0, sizeof phdr);
-               wrle32(p_type, &phdr.p_type);
-               wrle32(data_offset, &phdr.p_offset);
-               wrle32(s->address, &phdr.p_vaddr);
-               wrle32(s->address, &phdr.p_paddr);
-               wrle32(p_flags, &phdr.p_flags);
-               wrle32(align, &phdr.p_align);
-
-               address = s->address;
-               offset  = data_offset + filesize;
-
-               while (p_type == PT_LOAD &&
-                      (s->sh_flags & SHF_ALLOC) &&
-                      s->next &&
-                      s->next->sh_type == s->sh_type &&
-                      s->next->sh_flags == s->sh_flags &&
-                      s->next->align <= s->align &&
-                      (s->next->address ==
-                       align_up(address+memsize, s->next->align))) {
-                       s = s->next;
-                       align = UINT32_C(1) << s->align;
-                       pad = (s->address - (address+memsize)) & (align-1);
-                       if (s->sh_type != SHT_NOBITS) {
-                               assert(pad ==
-                                      ((s->address - (offset+filesize))
-                                       & (align-1)));
-                               filesize += s->length + pad;
+               if (s->sh_type == SHT_NOBITS)
+                       pad = 0;
+               else
+                       pad = (s->address - data_offset) & (align-1);
+
+               data_offset += pad;
+
+               if (ptype != PT_NULL) {
+                       p_flags = 0;
+                       if (s->sh_flags & SHF_ALLOC) /* Somewhat cheesy... */
+                               p_flags |= PF_R;
+                       if (s->sh_flags & SHF_EXECINSTR)
+                               p_flags |= PF_X;
+                       if (s->sh_flags & SHF_WRITE)
+                               p_flags |= PF_W;
+
+                       memset(&phdr, 0, sizeof phdr);
+                       wrle32(ptype, &phdr.p_type);
+                       wrle32(data_offset, &phdr.p_offset);
+                       wrle32(s->address, &phdr.p_vaddr);
+                       wrle32(s->address, &phdr.p_paddr);
+                       wrle32(p_flags, &phdr.p_flags);
+                       wrle32(align, &phdr.p_align);
+
+                       address = s->address;
+                       offset  = data_offset + filesize;
+
+                       while (merge_phdrs(s)) {
+                               s = s->next;
+                               align = UINT32_C(1) << s->align;
+                               pad = (s->address - (address+memsize))
+                                       & (align-1);
+                               if (s->sh_type != SHT_NOBITS) {
+                                       assert(pad == ((s->address - offset)
+                                                      & (align-1)));
+                                       filesize += s->length + pad;
+                                       offset   += s->length + pad;
+                               }
+                               memsize += s->length + pad;
                        }
-                       memsize += s->length + pad;
-               }
 
-               wrle32(filesize, &phdr.p_filesz);
-               wrle32(memsize, &phdr.p_memsz);
+                       wrle32(filesize, &phdr.p_filesz);
+                       wrle32(memsize, &phdr.p_memsz);
 
-               file_offset += c_fwrite(&phdr, sizeof phdr, out);
+                       file_offset += c_fwrite(&phdr, sizeof phdr, out);
+               }
                data_offset += filesize;
        }
 
@@ -221,13 +246,13 @@ static int gen_elf(struct segment *segs, Elf32_Ehdr *ehdr, FILE *out)
        return 0;
 }
 
-int output_elf(struct segment *segs, addr_t entry, FILE *out)
+
+static int do_elf(struct segment *segs, addr_t entry, FILE *out)
 {
        Elf32_Ehdr ehdr;
        int rv;
 
        memset(&ehdr, 0, sizeof ehdr);
-       segs = sort_segments(segs);
 
        ehdr.e_ident[EI_MAG0]    = ELFMAG0;
        ehdr.e_ident[EI_MAG1]    = ELFMAG1;
@@ -253,3 +278,45 @@ int output_elf(struct segment *segs, addr_t entry, FILE *out)
        /* The real run */
        return gen_elf(segs, &ehdr, out);
 }
+
+int output_elf(struct segment *segs, addr_t entry, FILE *out)
+{
+       segs = sort_segments(segs);
+       return do_elf(segs, entry, out);
+}
+
+int output_multiboot(struct segment *segs, addr_t entry, FILE *out)
+{
+       struct segment mb_seg;
+       struct multiboot_header mb_hdr;
+       uint32_t csum;
+       char *p;
+
+       segs = sort_segments(segs);
+
+       /* Stick a Multiboot header in a segment at
+          the beginning of the file. */
+       mb_seg.next     = segs;
+       mb_seg.length   = sizeof mb_hdr;
+       mb_seg.align    = 2;
+       mb_seg.address  = 0;
+       mb_seg.sh_type  = SHT_PROGBITS;
+       mb_seg.sh_flags = 0;    /* No ALLOC, EXECINSTR, or WRITE */
+       mb_seg.name     = ".multiboot";
+       mb_seg.data     = &mb_hdr;
+       segs = &mb_seg;
+
+       /* Multiboot header */
+       memset(&mb_hdr, 0, sizeof mb_hdr);
+       wrle32(MULTIBOOT_MAGIC,&mb_hdr.magic);
+       wrle32(MB_FLAG_PAGE, &mb_hdr.flags);
+
+       csum = 0;
+       for (p = (char *)&mb_hdr; p < (char *)(&mb_hdr+1); p += 4) {
+               csum += rdle32((const uint32_t *)p);
+       }
+       wrle32(-csum, &mb_hdr.checksum);
+
+       /* Generate the ELF image */
+       return do_elf(segs, entry, out);
+}
diff --git a/main.c b/main.c
index 133af9e..cc3eadd 100644 (file)
--- a/main.c
+++ b/main.c
@@ -38,6 +38,7 @@ const struct option long_options[] = {
        {"initrd",      1, 0, 'i'},
        {"output",      1, 0, 'o'},
        {"elf",         0, 0, 'E'},
+       {"multiboot",   0, 0, 'M'},
        {"nbi",         0, 0, 'N'},
        {"help",        0, 0, 'h'},
        {"version",     0, 0, 'V'},
@@ -56,6 +57,7 @@ static void usage(int err)
                "supported)\n"
                "  --output       -o    output filename (default stdout)\n"
                "  --elf          -E    output in ELF format (default)\n"
+               "  --multiboot    -M    output in Multiboot ELF format\n"
                "  --nbi          -N    output in NBI format\n"
                "  --help         -h    display this help text\n"
                "  --version      -V    print the program version\n",
@@ -103,6 +105,9 @@ int main(int argc, char *argv[])
                case 'E':
                        opt.output = output_elf;
                        break;
+               case 'M':
+                       opt.output = output_multiboot;
+                       break;
                case 'N':
                        opt.output = output_nbi;
                        break;
diff --git a/multiboot.h b/multiboot.h
new file mode 100644 (file)
index 0000000..c567dca
--- /dev/null
@@ -0,0 +1,44 @@
+/* ----------------------------------------------------------------------- *
+ *   
+ *   Copyright 2008 rPath, Inc. - All Rights Reserved
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ *   Boston MA 02110-1301, USA; either version 2 of the License, or
+ *   (at your option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * multiboot.h
+ *
+ * Multiboot specification header
+ */
+
+#ifndef MULTIBOOT_H
+#define MULTIBOOT_H
+
+struct multiboot_header {
+       uint32_t magic;
+       uint32_t flags;
+       uint32_t checksum;
+       uint32_t header_addr;
+       uint32_t load_addr;
+       uint32_t load_end_addr;
+       uint32_t entry_addr;
+       uint32_t mode_type;
+       uint32_t width;
+       uint32_t height;
+       uint32_t depth;
+};
+
+#define MULTIBOOT_MAGIC 0x1badb002
+
+#define MB_FLAG_PAGE   0x00000001 /* Page align */
+#define MB_FLAG_MEMINFO        0x00000002 /* Need memory information */
+#define MB_FLAG_VIDINFO 0x00000004 /* Video information specified */
+#define MB_FLAG_MBLOAD 0x00010000 /* Use the load info in Multiboot header */
+                                  /* (obligatory for non-ELF modules) */
+
+#endif /* MULTIBOOT_H */
index 1372781..46d9d35 100644 (file)
--- a/segment.h
+++ b/segment.h
@@ -32,13 +32,13 @@ struct segment {
        addr_t address;
        uint32_t sh_type;       /* Uses ELF constants, in host byte order */
        uint32_t sh_flags;      /* d:o */
-       uint32_t p_flags;
        const char *name;
        const void *data;
 };
 
 struct segment *sort_segments(struct segment *list);
 int output_elf(struct segment *segs, addr_t entry, FILE *out);
+int output_multiboot(struct segment *segs, addr_t entry, FILE *out);
 int output_nbi(struct segment *segs, addr_t entry, FILE *out);
 
 #endif /* SEGMENT_H */
index ded45c4..0baede6 100644 (file)
@@ -9,7 +9,7 @@
 .\"   (at your option) any later version; incorporated herein by reference.
 .\"
 .\" -----------------------------------------------------------------------
-.TH WRAPLINUX "1" "11 January 2008" "wraplinux" "H. Peter Anvin"
+.TH WRAPLINUX "1" "27 May 2008" "wraplinux" "H. Peter Anvin"
 .SH NAME
 wraplinux \- Create an ELF or NBI file from a Linux kernel with optional initrds.
 .SH SYNOPSIS
@@ -35,6 +35,9 @@ Kernel commandline parameters, enclosed in double-quotes.
 \fB\-E\fP, \fB\-\-elf\fP
 Output in ELF format (default).
 .TP
+\fB\-M\fP, \fB\-\-multiboot\fP
+Output in ELF format with a Multiboot header.
+.TP
 \fB\-N\fP, \fB\-\-NBI\fP
 Output in NBI format.
 .TP