/* p_vmlinx.cpp -- pack vmlinux ET_EXEC file (before bootsect or setup)

   This file is part of the UPX executable compressor.

   Copyright (C) John Reiser
   Copyright (C) Markus Franz Xaver Johannes Oberhumer
   Copyright (C) Laszlo Molnar
   All Rights Reserved.

   UPX and the UCL library are free software; you can redistribute them
   and/or modify them under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   Markus F.X.J. Oberhumer              Laszlo Molnar
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>

   John Reiser
   <jreiser@users.sourceforge.net>
 */


#define ALLOW_INT_PLUS_MEMBUFFER 1
#include "conf.h"

#include "file.h"
#include "filter.h"
#include "packer.h"
#include "p_vmlinx.h"
#define WANT_EHDR_ENUM 1
#include "p_elf_enum.h"
#include "linker.h"

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i386-linux.kernel.vmlinux.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/amd64-linux.kernel.vmlinux.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/arm.v5a-linux.kernel.vmlinux.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/armeb.v5a-linux.kernel.vmlinux.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/powerpc-linux.kernel.vmlinux.h"


/*************************************************************************
//
**************************************************************************/

template <class T>
PackVmlinuxBase<T>::PackVmlinuxBase(InputFile *f,
                                    unsigned e_machine, unsigned elfclass, unsigned elfdata,
                                    char const *const boot_label) :
    super(f),
    my_e_machine(e_machine), my_elfclass(elfclass), my_elfdata(elfdata),
    my_boot_label(boot_label),
    n_ptload(0), phdri(nullptr), shdri(nullptr), shstrtab(nullptr)
{
    ElfClass::compileTimeAssertions();
    bele = N_BELE_CTP::getRTP((const BeLePolicy*) nullptr);
}

template <class T>
PackVmlinuxBase<T>::~PackVmlinuxBase()
{
    delete [] phdri;
    delete [] shdri;
    delete [] shstrtab;
}

template <class T>
int PackVmlinuxBase<T>::getStrategy(Filter &/*ft*/)
{
    // Called just before reading and compressing each block.
    // Might want to adjust blocksize, etc.

    // If user specified the filter, then use it (-2==strategy).
    // Else try the first two filters, and pick the better (2==strategy).
    return (opt->no_filter ? -3 : ((opt->filter > 0) ? -2 : 2));
}

template <class T>
int __acc_cdecl_qsort
PackVmlinuxBase<T>::compare_Phdr(void const *aa, void const *bb)
{
    Phdr const *const a = (Phdr const *)aa;
    Phdr const *const b = (Phdr const *)bb;
    unsigned const xa = a->p_type - Phdr::PT_LOAD;
    unsigned const xb = b->p_type - Phdr::PT_LOAD;
            if (xa < xb)         return -1;  // PT_LOAD first
            if (xa > xb)         return  1;
    if (a->p_paddr < b->p_paddr) return -1;  // ascending by .p_paddr
    if (a->p_paddr > b->p_paddr) return  1;
    // What could remain?
    // try to make sort order deterministic and just compare more fields
#define CMP(field) \
    if (a->field != b->field) return a->field < b->field ? -1 : 1
    CMP(p_offset);
    CMP(p_vaddr);
    CMP(p_filesz);
    CMP(p_memsz);
    CMP(p_flags);
    CMP(p_align);
#undef CMP
    return 0;
}

template <class T>
typename T::Shdr const *PackVmlinuxBase<T>::getElfSections()
{
    unsigned const e_shnum = ehdri.e_shnum;
    if (ehdri.e_shentsize != sizeof(*shdri)
    ||  file_size_u < ehdri.e_shoff
    ||  file_size_u < ehdri.e_shoff + mem_size(ehdri.e_shentsize, e_shnum)) {
        infoWarning("bad ElfXX_Shdrs");
        return nullptr;
    }
    shdri = new Shdr[(unsigned) e_shnum];
    fi->seek(ehdri.e_shoff, SEEK_SET);
    fi->readx(shdri, e_shnum * sizeof(*shdri));
    unsigned const e_shstrndx = ehdri.e_shstrndx;
    if (e_shnum <= e_shstrndx) {
        infoWarning("bad .e_shstrndx %#x", e_shstrndx);
        return nullptr;
    }
    Shdr const *p = &shdri[e_shstrndx];
    if (Shdr::SHT_STRTAB==p->sh_type
    &&  p->sh_offset  <= (file_size_u - sizeof(*shdri))
    &&  p->sh_size    <= (file_size_u - p->sh_offset)
    &&  p->sh_name    <= (file_size_u - p->sh_offset)
    &&  10            <= (file_size_u - p->sh_name)
        // 10 == (1+ strlen(".shstrtab"))
    ) {
        if (p->sh_size <= p->sh_name) {
            infoWarning("bad .shstrtab _Shdr[%u]", (unsigned)ehdri.e_shstrndx);
            return nullptr;
        }
        shstrtab = new char[1+ p->sh_size];
        fi->seek(p->sh_offset, SEEK_SET);
        fi->readx(shstrtab, p->sh_size);
        shstrtab[p->sh_size] = '\0';
        if (0==strcmp(".shstrtab", shstrtab + p->sh_name)) {
            return p;
        }
    }
    return nullptr;
}

template <class T>
tribool PackVmlinuxBase<T>::canPack()
{
    fi->seek(0, SEEK_SET);
    fi->readx(&ehdri, sizeof(ehdri));

    // now check the ELF header
    if (memcmp(&ehdri, "\x7f\x45\x4c\x46", 4)
    ||  ehdri.e_ident[Ehdr::EI_CLASS] != my_elfclass
    ||  ehdri.e_ident[Ehdr::EI_DATA] != my_elfdata
    ||  ehdri.e_ident[Ehdr::EI_VERSION] != Ehdr::EV_CURRENT
    ||  !memcmp(&ehdri.e_ident[8], "FreeBSD", 7)  // branded
    ||  ehdri.e_machine != my_e_machine
    ||  ehdri.e_version != 1  // version
    ||  ehdri.e_ehsize != sizeof(ehdri)  // different <elf.h> ?
    ) {
        return false;
    }

    // additional requirements for vmlinux
    if (ehdri.e_type != Ehdr::ET_EXEC
    ||  ehdri.e_phoff != sizeof(ehdri)  // Phdr not contiguous with Ehdr
    ||  ehdri.e_phentsize!=sizeof(Phdr)
    ||  !is_valid_e_entry(ehdri.e_entry)
    ) {
        return false;
    }

    // A Linux kernel must have a __ksymtab section. [??]
    Shdr const *p, *const shstrsec = getElfSections();
    if (nullptr==shstrsec) {
        return false;
    }
    {
        int j;
        for (p = shdri, j= ehdri.e_shnum; --j>=0; ++p) {
            if (Shdr::SHT_PROGBITS==p->sh_type
            && p->sh_name < shstrsec->sh_size
            && 0==strcmp("__ksymtab", p->sh_name + shstrtab)) {
                break;
            }
        }
        if (j < 0) {
            return false;
        }
    }

    phdri = new Phdr[(unsigned) ehdri.e_phnum];
    fi->seek(ehdri.e_phoff, SEEK_SET);
    fi->readx(phdri, ehdri.e_phnum * sizeof(*phdri));

    // Put PT_LOAD together at the beginning, ascending by .p_paddr.
    upx_qsort(phdri, ehdri.e_phnum, sizeof(*phdri), compare_Phdr);

    // Find convex hull of physical addresses, and count the PT_LOAD.
    // Ignore ".bss": .p_filesz < .p_memsz
    unsigned phys_lo= ~0u, phys_hi= 0u;
    for (unsigned j = 0; j < ehdri.e_phnum; ++j) {
        if (Phdr::PT_LOAD==phdri[j].p_type) {
            // Check for general sanity (not necessarily required.)
            if (0xfff & (phdri[j].p_offset | phdri[j].p_paddr
                       | phdri[j].p_align  | phdri[j].p_vaddr) ) {
                return false;
            }
            if (phys_lo > phdri[j].p_paddr) {
                phys_lo = phdri[j].p_paddr;
            }
            if (phys_hi < (phdri[j].p_filesz + phdri[j].p_paddr)) {
                phys_hi = (phdri[j].p_filesz + phdri[j].p_paddr);
            }
            ++n_ptload;
        }
    }
    paddr_min = phys_lo;
    sz_ptload = phys_hi - phys_lo;
    return 0 < n_ptload;
}

#include "p_elf.h"

template <class T>
void PackVmlinuxBase<T>::pack(OutputFile *fo)
{
    unsigned fo_off = 0;
    Ehdr ehdro;
    TE32 tmp_u32;

    // nullptr
    // .text(PT_LOADs) .note(1st page) .note(rest)
    // .shstrtab .symtab .strtab
    Shdr shdro[1+3+3];
    memset(shdro, 0, sizeof(shdro));

    ibuf.alloc(file_size);
    obuf.allocForCompression(file_size);

    // .e_ident, .e_machine, .e_version, .e_flags
    memcpy(&ehdro, &ehdri, sizeof(ehdro));
    ehdro.e_type = Ehdr::ET_REL;
    ehdro.e_entry = 0;
    ehdro.e_phoff = 0;
    ehdro.e_shoff = sizeof(ehdro);
    ehdro.e_phentsize = 0;
    ehdro.e_phnum = 0;
    ehdro.e_shnum = 1+3+3;
    ehdro.e_shstrndx = 4;
    fo->write(&ehdro, sizeof(ehdro)); fo_off+= sizeof(ehdro);
    fo->write(shdro, sizeof(shdro)); fo_off+= sizeof(shdro);

// Notice overlap [containment] of physical PT_LOAD[2] into PTLOAD[1]
// in this vmlinux for x86_64 from Fedora Core 6 on 2007-01-07:
//Program Headers:
//  Type           Offset             VirtAddr           PhysAddr
//                 FileSiz            MemSiz              Flags  Align
//  LOAD           0x0000000000200000 0xffffffff80200000 0x0000000000200000
//                 0x000000000034bce8 0x000000000034bce8  R E    200000
//  LOAD           0x000000000054c000 0xffffffff8054c000 0x000000000054c000
//                 0x00000000000ed004 0x00000000001702a4  RWE    200000
//  LOAD           0x0000000000800000 0xffffffffff600000 0x00000000005f5000
//                 0x0000000000000c08 0x0000000000000c08  RWE    200000
//  NOTE           0x0000000000000000 0x0000000000000000 0x0000000000000000
//                 0x0000000000000000 0x0000000000000000  R      8
// Therefore we must "compose" the convex hull to be loaded.

    ph.u_len = sz_ptload;
    memset(ibuf, 0, sz_ptload);
    for (unsigned j = 0; j < ehdri.e_phnum; ++j) {
        if (Phdr::PT_LOAD==phdri[j].p_type) {
            fi->seek(phdri[j].p_offset, SEEK_SET);
            fi->readx(ibuf + ((unsigned) phdri[j].p_paddr - paddr_min), phdri[j].p_filesz);
        }
    }
    checkAlreadyPacked(ibuf + (ph.u_len - 1024), 1024);

    // prepare filter
    ph.filter = 0;
    Filter ft(ph.level);
    ft.buf_len = ph.u_len;
    ft.addvalue = 0;  // we are independent of actual runtime address; see ckt32

    upx_compress_config_t cconf; cconf.reset();
    // limit stack size needed for runtime decompression
    cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack

    unsigned ppc32_extra = 0;
    if (Ehdr::EM_PPC==my_e_machine) {
        // output layout:
        //      .long UPX_MAGIC_LE32
        //      .long L20 - L10
        // L10:
        //      b_info for Ehdr; compressed Ehdr; .balign 4  // one block only
        //      b_info for LOAD; compressed LOAD; .balign 4  // possibly many blocks
        //          // This allows per-block filters!
        // L20:
        //      b f_decompress
        // +4:  f_unfilter(char *buf, unsigned len, unsigned cto8, unsigned ftid)
        //          // Code for multiple filters can "daisy chain" on ftid.
        //      f_decompress(char const *src, unsigned  src_len,
        //                   char       *dst, unsigned *dst_len, int method)
        unsigned tmp;
        tmp = UPX_MAGIC_LE32; fo->write(&tmp, sizeof(tmp)); fo_off += sizeof(tmp);
        tmp = 0;              fo->write(&tmp, sizeof(tmp)); fo_off += sizeof(tmp);
        ppc32_extra += 2*sizeof(tmp);
        unsigned const len_unc = sizeof(ehdri) + sizeof(Phdr) * ehdri.e_phnum;
        MemBuffer unc_hdr(len_unc);
        MemBuffer cpr_hdr; cpr_hdr.allocForCompression(len_unc);
        memcpy(&unc_hdr[0],             &ehdri, sizeof(ehdri));
        memcpy(&unc_hdr[sizeof(ehdri)],  phdri, sizeof(Phdr) * ehdri.e_phnum);
        unsigned len_cpr = 0;
        int const r = upx_compress(unc_hdr, len_unc, cpr_hdr, &len_cpr,
            nullptr, ph.method, 10, nullptr, nullptr );
        if (UPX_E_OK!=r || len_unc<=len_cpr)  // FIXME: allow no compression
            throwInternalError("Ehdr compression failed");

        packed_struct(b_info) { // 12-byte header before each compressed block
            NE32 sz_unc;  // uncompressed_size
            NE32 sz_cpr;  //   compressed_size
            unsigned char b_method;  // compression algorithm
            unsigned char b_ftid;  // filter id
            unsigned char b_cto8;  // filter parameter
            unsigned char b_unused;  // FIXME: !=0  for partial-block unfilter
            // NE32 f_offset, f_len;  // only if    partial-block unfilter
        };

        struct b_info hdr_info;
        set_be32(&hdr_info.sz_unc, len_unc);
        set_be32(&hdr_info.sz_cpr, len_cpr);
        hdr_info.b_method = ph.method;
        hdr_info.b_ftid = 0;
        hdr_info.b_cto8 = 0;
        hdr_info.b_unused = 0;
        fo->write(&hdr_info, sizeof(hdr_info)); fo_off += sizeof(hdr_info);
        unsigned const frag = (3& (0u-len_cpr));
        ppc32_extra += sizeof(hdr_info) + len_cpr + frag;
        fo_off += len_cpr + frag;
        memset(&cpr_hdr[len_cpr], 0, frag);  // valgrind only
        fo->write(cpr_hdr, len_cpr + frag);

        // Partial filter: .text and following contiguous SHF_EXECINSTR
        upx_bytep f_ptr = ibuf;
        unsigned  f_len = 0;
        Shdr const *shdr = 1+ shdri;  // skip empty shdr[0]
        if (ft.buf_len==0  // not specified yet  FIXME: was set near construction
        && (Shdr::SHF_ALLOC     & shdr->sh_flags)
        && (Shdr::SHF_EXECINSTR & shdr->sh_flags)) {
            // shdr[1] is instructions (probably .text)
            f_ptr = ibuf + (unsigned) (shdr->sh_offset - phdri[0].p_offset);
            f_len = shdr->sh_size;
            ++shdr;
            for (int j= ehdri.e_shnum - 2; --j>=0; ++shdr) {
                unsigned prev_end = shdr[-1].sh_size + shdr[-1].sh_offset;
                prev_end += ~(0u-shdr[0].sh_addralign) & (0u-prev_end);  // align_up
                if ((Shdr::SHF_ALLOC     & shdr->sh_flags)
                &&  (Shdr::SHF_EXECINSTR & shdr->sh_flags)
                &&  shdr[0].sh_offset==prev_end) {
                    f_len += shdr->sh_size;
                }
                else {
                    break;
                }
            }
        }
        else { // ft.buf_len already specified, or Shdr[1] not instructions
            f_ptr = ibuf;
            f_len = ph.u_len;
        }
        compressWithFilters(ibuf, ph.u_len, obuf,
            f_ptr, f_len,  // filter range
            nullptr, 0,  // hdr_ptr, hdr_len
            &ft, 512, &cconf, getStrategy(ft));

        set_be32(&hdr_info.sz_unc, ph.u_len);
        set_be32(&hdr_info.sz_cpr, ph.c_len);
        hdr_info.b_ftid = ft.id;
        hdr_info.b_cto8 = ft.cto;
        if (ph.u_len!=f_len) {
            hdr_info.b_unused = 1;  // flag for partial filter
        }
        fo->write(&hdr_info, sizeof(hdr_info)); fo_off += sizeof(hdr_info);
        ppc32_extra += sizeof(hdr_info);
        if (ph.u_len!=f_len) {
            set_be32(&hdr_info.sz_unc, f_ptr - (upx_bytep) ibuf);
            set_be32(&hdr_info.sz_cpr, f_len);
            fo->write(&hdr_info, 2*sizeof(unsigned)); fo_off += 2*sizeof(unsigned);
            ppc32_extra += 2*sizeof(unsigned);
        }
    }
    else {
        compressWithFilters(&ft, 512, &cconf, getStrategy(ft));
    }
    unsigned const txt_c_len = ph.c_len;

    const unsigned lsize = getLoaderSize();

    defineDecompressorSymbols();
    defineFilterSymbols(&ft);
    relocateLoader();

    MemBuffer loader(lsize);
    memcpy(loader, getLoader(), lsize);
    patchPackHeader(loader, lsize);

#define shstrtab local_shstrtab // avoid -Wshadow warning
    char const shstrtab[]= "\0.text\0.note\0.shstrtab\0.symtab\0.strtab";
    char const *p = shstrtab;
    while (0!=*p++) ;
    shdro[1].sh_name = ptr_diff(p, shstrtab);
    shdro[1].sh_type = Shdr::SHT_PROGBITS;
    shdro[1].sh_flags = Shdr::SHF_ALLOC | Shdr::SHF_EXECINSTR;
    shdro[1].sh_offset = fo_off - ppc32_extra;
    shdro[1].sh_size = ppc32_extra + txt_c_len + lsize;  // plus more ...
    shdro[1].sh_addralign = 1;  // default

    fo_off += write_vmlinux_head(fo, &shdro[1]);
    fo->write(obuf, txt_c_len); fo_off += txt_c_len;
    unsigned const a = (shdro[1].sh_addralign -1) & (0u-(ppc32_extra + txt_c_len));
    if (0!=a) { // align
        fo_off += a;
        shdro[1].sh_size += a;
        fo->seek(a, SEEK_CUR);
    }
    fo->write(loader, lsize); fo_off += lsize;

#if 0
    printf("%-13s: compressed   : %8u bytes\n", getName(), txt_c_len);
    printf("%-13s: decompressor : %8u bytes\n", getName(), lsize);
#endif
    verifyOverlappingDecompression();

    // .note with 1st page --------------------------------
    ph.u_len = phdri[0].p_offset;
    fi->seek(0, SEEK_SET);
    fi->readx(ibuf, ph.u_len);
    compress(ibuf, ph.u_len, obuf, &cconf);

    while (0!=*p++) ;
    shdro[2].sh_name = ptr_diff(p, shstrtab);
    shdro[2].sh_type = Shdr::SHT_NOTE;
    shdro[2].sh_offset = fo_off;
    shdro[2].sh_size = sizeof(ph.u_len) + ph.c_len;
    shdro[2].sh_addralign = 1;
    tmp_u32 = ph.u_len; fo->write(&tmp_u32, 4);
    fo->write(obuf, ph.c_len); fo_off += shdro[2].sh_size;

    // .note with rest     --------------------------------
    ph.u_len = file_size - (sz_ptload + phdri[0].p_offset);
    fi->seek(sz_ptload + phdri[0].p_offset, SEEK_SET);
    fi->readx(ibuf, ph.u_len);

    // Temporarily decrease ph.level by about (1+ log2(sz_rest / sz_ptload))
    // to avoid spending unreasonable effort compressing large symbol tables
    // that are discarded 99.9% of the time anyway.
    int const old_level = ph.level;
    for (unsigned v = ((ph.u_len>>3) + ph.u_len) / sz_ptload; 0 < v; v>>=1) {
        if (0== --ph.level) {
            ph.level = 1;
        }
    }
    compress(ibuf, ph.u_len, obuf, &cconf);
    ph.level = old_level;

    // while (0!=*p++) ;  // name is the same
    shdro[3].sh_name = ptr_diff(p, shstrtab);
    shdro[3].sh_type = Shdr::SHT_NOTE;
    shdro[3].sh_offset = fo_off;
    shdro[3].sh_size = sizeof(ph.u_len) + ph.c_len;
    shdro[3].sh_addralign = 1;
    tmp_u32 = ph.u_len; fo->write(&tmp_u32, 4);
    fo->write(obuf, ph.c_len); fo_off += shdro[3].sh_size;

    while (0!=*p++) ;
    shdro[4].sh_name = ptr_diff(p, shstrtab);
    shdro[4].sh_type = Shdr::SHT_STRTAB;
    shdro[4].sh_offset = fo_off;
    shdro[4].sh_size = sizeof(shstrtab);  // already includes terminating '\0'
    shdro[4].sh_addralign = 1;
    fo->write(shstrtab, shdro[4].sh_size); fo_off += shdro[4].sh_size;

    fo_off = ~3 & (3+ fo_off); fo->seek(fo_off, SEEK_SET);
    while (0!=*p++) ;
    shdro[5].sh_name = ptr_diff(p, shstrtab);
    shdro[5].sh_type = Shdr::SHT_SYMTAB;
    shdro[5].sh_offset = fo_off;
    shdro[5].sh_size = ((Ehdr::EM_PPC==my_e_machine) + 5)*sizeof(Sym);
    //shdro[5].sh_flags = Shdr::SHF_INFO_LINK;
    shdro[5].sh_link = 6;  // to .strtab for symbols
    shdro[5].sh_info = 1+3;  // number of non-global symbols [binutils/bfd/elf.c]
    shdro[5].sh_addralign = 4;
    shdro[5].sh_entsize = sizeof(Sym);

    Sym sec_sym;

    // Symbol 0; no references, but bfd demands it.
    memset(&sec_sym, 0, sizeof(sec_sym));
    fo->write(&sec_sym, sizeof(sec_sym)); fo_off += sizeof(sec_sym);

    // Each section before .shstrtab needs a symbol.
    sec_sym.st_info = sec_sym.make_st_info(Sym::STB_LOCAL, Sym::STT_SECTION);
    sec_sym.st_other = Sym::STV_DEFAULT;
    sec_sym.st_shndx = 1;  // .text
    fo->write(&sec_sym, sizeof(sec_sym)); fo_off += sizeof(sec_sym);
    sec_sym.st_shndx = 2;  // .note
    fo->write(&sec_sym, sizeof(sec_sym)); fo_off += sizeof(sec_sym);
    sec_sym.st_shndx = 3;  // .note
    fo->write(&sec_sym, sizeof(sec_sym)); fo_off += sizeof(sec_sym);

    // the symbol we care about
    Sym unc_ker;
    unc_ker.st_name = 1;  // 1 byte into strtab
    unc_ker.st_value = 0;
    unc_ker.st_size = ppc32_extra + txt_c_len;
    unc_ker.st_info = unc_ker.make_st_info(Sym::STB_GLOBAL, Sym::STT_FUNC);
    unc_ker.st_other = Sym::STV_DEFAULT;
    unc_ker.st_shndx = 1;  // .text
    fo->write(&unc_ker, sizeof(unc_ker)); fo_off += sizeof(unc_ker);

    unsigned const lablen = strlen(my_boot_label);
    if (Ehdr::EM_PPC==my_e_machine) {
        unc_ker.st_name += 1+ lablen;
        unc_ker.st_value = unc_ker.st_size;
        unc_ker.st_size = 0;
        fo->write(&unc_ker, sizeof(unc_ker)); fo_off += sizeof(unc_ker);
    }
    while (0!=*p++) ;
    shdro[6].sh_name = ptr_diff(p, shstrtab);
    shdro[6].sh_type = Shdr::SHT_STRTAB;
    shdro[6].sh_offset = fo_off;
    shdro[6].sh_size = 2+ lablen + (Ehdr::EM_PPC==my_e_machine)*(1+ 12);  // '\0' before and after
    shdro[6].sh_addralign = 1;
    fo->seek(1, SEEK_CUR);  // the '\0' before
    fo->write(my_boot_label, 1+ lablen);  // include the '\0' terminator
    if (Ehdr::EM_PPC==my_e_machine) {
        fo->write("_vmlinux_end", 1+ 12); fo_off += 1+ 12;
    }
    fo_off += 2+ lablen;

    fo->seek(0, SEEK_SET);
    fo->write(&ehdro, sizeof(ehdro));
    fo->write(&shdro[0], sizeof(shdro));
    if (Ehdr::EM_PPC==my_e_machine) {
        fo->seek(sizeof(unsigned), SEEK_CUR);
        set_be32(&ppc32_extra, ppc32_extra - 2*sizeof(unsigned) + txt_c_len);
        fo->write(&ppc32_extra, sizeof(ppc32_extra));
    }

    if (!checkFinalCompressionRatio(fo))
        throwNotCompressible();
#undef shstrtab
}

template <class T>
tribool PackVmlinuxBase<T>::canUnpack()
{
    fi->seek(0, SEEK_SET);
    fi->readx(&ehdri, sizeof(ehdri));

    // now check the ELF header
    if (memcmp(&ehdri, "\x7f\x45\x4c\x46", 4)
    ||  ehdri.e_ident[Ehdr::EI_CLASS] != my_elfclass
    ||  ehdri.e_ident[Ehdr::EI_DATA] != my_elfdata
    ||  ehdri.e_ident[Ehdr::EI_VERSION] != Ehdr::EV_CURRENT
    ||  !memcmp(&ehdri.e_ident[8], "FreeBSD", 7)  // branded
    ||  ehdri.e_machine != my_e_machine
    ||  ehdri.e_version != 1  // version
    ||  ehdri.e_ehsize != sizeof(ehdri)  // different <elf.h> ?
    )
        return false;

    if (ehdri.e_type != Ehdr::ET_REL
    //i386 fails  ||  ehdri.e_shoff != sizeof(ehdri)  // Shdr not contiguous with Ehdr
    ||  ehdri.e_shentsize!=sizeof(Shdr)
    ||  ehdri.e_shnum < 4
    ||  (unsigned)file_size < (ehdri.e_shnum * sizeof(Shdr) + ehdri.e_shoff)
    )
        return false;

    // find the .shstrtab section
    Shdr const *const shstrsec = getElfSections();
    if (nullptr==shstrsec) {
        return false;
    }

    // check for .text .note .note  and sane (.sh_size + .sh_offset)
    p_note0 = p_note1 = p_text = nullptr;
    int j;
    Shdr *p;
    for (p= shdri, j= ehdri.e_shnum; --j>=0; ++p) {
        if ((unsigned)file_size < (p->sh_size + p->sh_offset)
        || (5+ p->sh_name) < p->sh_name  // wrap: ignore malformed
        ||  shstrsec->sh_size < (5+ p->sh_name) ) {
            continue;
        }
        if (0==strcmp(".text", shstrtab + p->sh_name)) {
            p_text = p;
        }
        if (0==strcmp(".note", shstrtab + p->sh_name)) {
            if (nullptr==p_note0) {
                p_note0 = p;
            } else
            if (nullptr==p_note1) {
                p_note1 = p;
            }
        }
    }
    if (nullptr==p_text || nullptr==p_note0 || nullptr==p_note1) {
        return false;
    }

    char buf[1024];
    fi->seek(p_text->sh_offset + p_text->sh_size - sizeof(buf), SEEK_SET);
    fi->readx(buf, sizeof(buf));
    if (!getPackHeader(buf, sizeof(buf)))
        return -1; // format is known, but definitely is not packed

    return true;
}

template <class T>
void PackVmlinuxBase<T>::unpack(OutputFile *fo)
{
    TE32 word;
    PackHeader const ph_tmp(ph);

    fi->seek(p_note0->sh_offset, SEEK_SET);
    fi->readx(&word, sizeof(word));
    ph.u_len = word;
    ph.c_len = p_note0->sh_size - sizeof(word);
    ibuf.alloc(ph.c_len);
    fi->readx(ibuf, ph.c_len);
    obuf.allocForDecompression(ph.u_len);
    decompress(ibuf, obuf, false);
    fo->write(obuf, ph.u_len);
    obuf.dealloc();
    ibuf.dealloc();

    ph = ph_tmp;
    if (!has_valid_vmlinux_head()) {
        throwCantUnpack(".text corrupted");
    }
    ibuf.alloc(ph.c_len);
    fi->readx(ibuf, ph.c_len);
    obuf.allocForDecompression(ph.u_len);
    decompress(ibuf, obuf);

    Filter ft(ph.level);
    ft.init(ph.filter, 0);
    ft.cto = (unsigned char) ph.filter_cto;
    ft.unfilter(obuf, ph.u_len);
    fo->write(obuf, ph.u_len);
    obuf.dealloc();
    ibuf.dealloc();

    fi->seek(p_note1->sh_offset, SEEK_SET);
    fi->readx(&word, sizeof(word));
    ph.u_len = word;
    ph.c_len = p_note1->sh_size - sizeof(word);
    ibuf.alloc(ph.c_len);
    fi->readx(ibuf, p_note1->sh_size - sizeof(ph.u_len));
    obuf.allocForDecompression(ph.u_len);
    decompress(ibuf, obuf, false);
    fo->write(obuf, ph.u_len);
    obuf.dealloc();
    ibuf.dealloc();

    ph = ph_tmp;
}


/*************************************************************************
//
**************************************************************************/

const int *PackVmlinuxI386::getCompressionMethods(int method, int level) const
{
    return Packer::getDefaultCompressionMethods_le32(method, level);
}


const int *PackVmlinuxI386::getFilters() const
{
    static const int filters[] = {
        0x49, 0x46,
    FT_END };
    return filters;
}

const int *PackVmlinuxARMEL::getCompressionMethods(int method, int level) const
{
    return Packer::getDefaultCompressionMethods_8(method, level);
}

const int *PackVmlinuxARMEB::getCompressionMethods(int method, int level) const
{
    return Packer::getDefaultCompressionMethods_8(method, level);
}

const int *PackVmlinuxPPC32::getCompressionMethods(int method, int level) const
{
    // No real dependency on LE32.
    return Packer::getDefaultCompressionMethods_le32(method, level);
}

const int *PackVmlinuxPPC64LE::getCompressionMethods(int method, int level) const
{
    // No real dependency on LE32.
    return Packer::getDefaultCompressionMethods_le32(method, level);
}


const int *PackVmlinuxARMEL::getFilters() const
{
    static const int f50[] = { 0x50, FT_END };
    return f50;
}

const int *PackVmlinuxARMEB::getFilters() const
{
    static const int f51[] = { 0x51, FT_END };
    return f51;
}

const int *PackVmlinuxPPC32::getFilters() const
{
    static const int fd0[] = { 0xd0, FT_END };
    return fd0;
}

const int *PackVmlinuxPPC64LE::getFilters() const
{
    static const int fd0[] = { 0xd0, FT_END };
    return fd0;
}

//
// Examples as of 2004-07-16 [readelf --segments vmlinux  # before fiddling]:
//
//----- kernel-2.6.7 plain [defconfig?]
//Program Headers(2):
//  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
//  LOAD           0x001000 0x00100000 0x00100000 0x1c7e61 0x1c7e61 R E 0x1000
//  LOAD           0x1c8e64 0x002c8e64 0x002c8e64 0x00000 0x00000 RW  0x1000
//
//----- kernel-2.6.7-1.488 Fedora Core 3 test 1
//Program Headers(5):
//  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
//  LOAD           0x001000 0x02100000 0x02100000 0x202246 0x202246 R E 0x1000
//  LOAD           0x204000 0xffff3000 0x02303000 0x00664 0x00664 R E 0x1000
//  LOAD           0x205000 0x02304000 0x02304000 0x43562 0x43562 R   0x1000
//  LOAD           0x249000 0x02348000 0x02348000 0x81800 0xcb0fc RWE 0x1000
//  STACK          0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
//
//----- kernel-2.6.18-1.2778 Fedora Core 6 test 3
//Program Headers(3)
//  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
//  LOAD           0x001000 0xc0400000 0x00400000 0x279820 0x279820 R E 0x1000
//  LOAD           0x27b000 0xc067a000 0x0067a000 0x10ee64 0x1b07e8 RWE 0x1000
//  NOTE           0x000000 0x00000000 0x00000000 0x00000 0x00000 R   0x4

bool PackVmlinuxI386::is_valid_e_entry(Addr e_entry)
{
    return 0==(0x000fffff & e_entry); // entry on whole 1 MiB
}


Linker* PackVmlinuxI386::newLinker() const
{
    return new ElfLinkerX86;
}


void PackVmlinuxI386::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_386, stub_i386_linux_kernel_vmlinux, sizeof(stub_i386_linux_kernel_vmlinux));
    addLoader("LINUX000",
              (0x40==(0xf0 & ft->id)) ? "LXCKLLT1" : (ft->id ? "LXCALLT1" : ""),
              "LXMOVEUP",
              getDecompressorSections(),
              nullptr
             );
    if (ft->id) {
        assert(ft->calls > 0);
        if (0x40==(0xf0 & ft->id)) {
            addLoader("LXCKLLT9", nullptr);
        }
        else {
            addLoader("LXCALLT9", nullptr);
        }
        addFilter32(ft->id);
    }
    addLoader("LINUX990",
              ((ph.first_offset_found == 1) ? "LINUX991" : ""),
              "LINUX992,IDENTSTR,UPX1HEAD", nullptr);
}

void PackVmlinuxAMD64::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_AMD64, stub_amd64_linux_kernel_vmlinux, sizeof(stub_amd64_linux_kernel_vmlinux));
    addLoader("LINUX000",
              (0x40==(0xf0 & ft->id)) ? "LXCKLLT1" : (ft->id ? "LXCALLT1" : ""),
              "LXMOVEUP",
              getDecompressorSections(),
              nullptr
             );
    if (ft->id) {
        assert(ft->calls > 0);
        if (0x40==(0xf0 & ft->id)) {
            addLoader("LXCKLLT9", nullptr);
        }
        else {
            addLoader("LXCALLT9", nullptr);
        }
        addFilter32(ft->id);
    }
    addLoader("LINUX990",
              ((ph.first_offset_found == 1) ? "LINUX991" : ""),
              "LINUX992,IDENTSTR,UPX1HEAD", nullptr);
}

bool PackVmlinuxARMEL::is_valid_e_entry(Addr e_entry)
{
    return 0xc0008000==e_entry;
}

bool PackVmlinuxARMEB::is_valid_e_entry(Addr e_entry)
{
    return 0xc0008000==e_entry;
}

bool PackVmlinuxPPC32::is_valid_e_entry(Addr e_entry)
{
    return 0xc0000000==e_entry;
}

bool PackVmlinuxPPC64LE::is_valid_e_entry(Addr e_entry)
{
    return 0xc0000000==e_entry;
}

Linker* PackVmlinuxARMEL::newLinker() const
{
    return new ElfLinkerArmLE;
}

Linker* PackVmlinuxARMEB::newLinker() const
{
    return new ElfLinkerArmBE;
}

Linker* PackVmlinuxPPC32::newLinker() const
{
    return new ElfLinkerPpc32;
}

Linker* PackVmlinuxPPC64LE::newLinker() const
{
    return new ElfLinkerPpc64le;
}


void PackVmlinuxARMEL::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_ARM, stub_arm_v5a_linux_kernel_vmlinux, sizeof(stub_arm_v5a_linux_kernel_vmlinux));
    addLoader("LINUX000", nullptr);
    if (ft->id) {
        assert(ft->calls > 0);
        addLoader("LINUX010", nullptr);
    }
    addLoader("LINUX020", nullptr);
    if (ft->id) {
        addFilter32(ft->id);
    }
    addLoader("LINUX030", nullptr);
         if (ph.method == M_NRV2E_8) addLoader("NRV2E", nullptr);
    else if (ph.method == M_NRV2B_8) addLoader("NRV2B", nullptr);
    else if (ph.method == M_NRV2D_8) addLoader("NRV2D", nullptr);
    else if (M_IS_LZMA(ph.method))   addLoader("LZMA_ELF00,LZMA_DEC10,LZMA_DEC30", nullptr);
    else throwBadLoader();
    addLoader("IDENTSTR,UPX1HEAD", nullptr);
}

void PackVmlinuxARMEB::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_ARM, stub_armeb_v5a_linux_kernel_vmlinux, sizeof(stub_armeb_v5a_linux_kernel_vmlinux));
    addLoader("LINUX000", nullptr);
    if (ft->id) {
        assert(ft->calls > 0);
        addLoader("LINUX010", nullptr);
    }
    addLoader("LINUX020", nullptr);
    if (ft->id) {
        addFilter32(ft->id);
    }
    addLoader("LINUX030", nullptr);
         if (ph.method == M_NRV2E_8) addLoader("NRV2E", nullptr);
    else if (ph.method == M_NRV2B_8) addLoader("NRV2B", nullptr);
    else if (ph.method == M_NRV2D_8) addLoader("NRV2D", nullptr);
    else if (M_IS_LZMA(ph.method))   addLoader("LZMA_ELF00,LZMA_DEC10,LZMA_DEC30", nullptr);
    else throwBadLoader();
    addLoader("IDENTSTR,UPX1HEAD", nullptr);
}

void PackVmlinuxPPC32::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_PPC, stub_powerpc_linux_kernel_vmlinux, sizeof(stub_powerpc_linux_kernel_vmlinux));
    addLoader("LINUX000", nullptr);
    if (ft->id) {
        assert(ft->calls > 0);
        addLoader("LINUX010", nullptr);
    }
    addLoader("LINUX020", nullptr);
    if (ft->id) {
        addFilter32(ft->id);
    }
    addLoader("LINUX030", nullptr);
         if (ph.method == M_NRV2E_LE32) addLoader("NRV2E,NRV_TAIL", nullptr);
    else if (ph.method == M_NRV2B_LE32) addLoader("NRV2B,NRV_TAIL", nullptr);
    else if (ph.method == M_NRV2D_LE32) addLoader("NRV2D,NRV_TAIL", nullptr);
    else if (M_IS_LZMA(ph.method))   addLoader("LZMA_ELF00,LZMA_DEC10,LZMA_DEC30", nullptr);
    else throwBadLoader();
    if (hasLoaderSection("CFLUSH"))
        addLoader("CFLUSH");

    addLoader("IDENTSTR,UPX1HEAD", nullptr);
}

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/powerpc64le-linux.kernel.vmlinux.h"
void PackVmlinuxPPC64LE::buildLoader(const Filter *ft)
{
    // prepare loader
    initLoader(EM_PPC, stub_powerpc64le_linux_kernel_vmlinux, sizeof(stub_powerpc64le_linux_kernel_vmlinux));
    addLoader("LINUX000", nullptr);
    if (ft->id) {
        assert(ft->calls > 0);
        addLoader("LINUX010", nullptr);
    }
    addLoader("LINUX020", nullptr);
    if (ft->id) {
        addFilter32(ft->id);
    }
    addLoader("LINUX030", nullptr);
         if (ph.method == M_NRV2E_LE32) addLoader("NRV2E,NRV_TAIL", nullptr);
    else if (ph.method == M_NRV2B_LE32) addLoader("NRV2B,NRV_TAIL", nullptr);
    else if (ph.method == M_NRV2D_LE32) addLoader("NRV2D,NRV_TAIL", nullptr);
    else if (M_IS_LZMA(ph.method))   addLoader("LZMA_ELF00,LZMA_DEC10,LZMA_DEC30", nullptr);
    else throwBadLoader();
    if (hasLoaderSection("CFLUSH"))
        addLoader("CFLUSH");

    addLoader("IDENTSTR,UPX1HEAD", nullptr);
}


static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/i386-linux.kernel.vmlinux-head.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/amd64-linux.kernel.vmlinux-head.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/arm.v5a-linux.kernel.vmlinux-head.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/armeb.v5a-linux.kernel.vmlinux-head.h"
static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/powerpc-linux.kernel.vmlinux-head.h"

unsigned PackVmlinuxI386::write_vmlinux_head(
    OutputFile *fo,
    Shdr *stxt
)
{
    // COMPRESSED_LENGTH
    fo->write(&stub_i386_linux_kernel_vmlinux_head[0],
        sizeof(stub_i386_linux_kernel_vmlinux_head)-(1+ 4) +1);
    TE32 tmp_u32; tmp_u32 = ph.c_len; fo->write(&tmp_u32, 4);

    stxt->sh_size += sizeof(stub_i386_linux_kernel_vmlinux_head);

    return sizeof(stub_i386_linux_kernel_vmlinux_head);
}

unsigned PackVmlinuxAMD64::write_vmlinux_head(
    OutputFile *fo,
    Shdr *stxt
)
{
    // COMPRESSED_LENGTH
    fo->write(&stub_amd64_linux_kernel_vmlinux_head[0],
        sizeof(stub_amd64_linux_kernel_vmlinux_head)-(1+ 4) +1);
    TE32 tmp_u32; tmp_u32 = ph.c_len; fo->write(&tmp_u32, 4);
printf("  Compressed length=0x%x\n", ph.c_len);
printf("UnCompressed length=0x%x\n", ph.u_len);

    stxt->sh_size += sizeof(stub_amd64_linux_kernel_vmlinux_head);

    return sizeof(stub_amd64_linux_kernel_vmlinux_head);
}

void PackVmlinuxARMEL::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    linker->defineSymbol(  "COMPRESSED_LENGTH", ph.c_len);
    linker->defineSymbol("UNCOMPRESSED_LENGTH", ph.u_len);
    linker->defineSymbol("METHOD", ph.method);
}

void PackVmlinuxARMEB::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    linker->defineSymbol(  "COMPRESSED_LENGTH", ph.c_len);
    linker->defineSymbol("UNCOMPRESSED_LENGTH", ph.u_len);
    linker->defineSymbol("METHOD", ph.method);
}

void PackVmlinuxPPC32::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    // linker->defineSymbol(  "COMPRESSED_LENGTH", ph.c_len);
    // linker->defineSymbol("UNCOMPRESSED_LENGTH", ph.u_len);
    // linker->defineSymbol("METHOD", ph.method);
}

void PackVmlinuxPPC64LE::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    // linker->defineSymbol(  "COMPRESSED_LENGTH", ph.c_len);
    // linker->defineSymbol("UNCOMPRESSED_LENGTH", ph.u_len);
    // linker->defineSymbol("METHOD", ph.method);
}

void PackVmlinuxI386::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    linker->defineSymbol("ENTRY_POINT", phdri[0].p_paddr);
    linker->defineSymbol("PHYSICAL_START", phdri[0].p_paddr);
}

void PackVmlinuxAMD64::defineDecompressorSymbols()
{
    super::defineDecompressorSymbols();
    // We assume a 32-bit boot loader, so we use the 32-bit convention
    // of "enter at the beginning" (startup_32).  The 64-bit convention
    // would be to use ehdri.e_entry (startup_64).
    linker->defineSymbol("ENTRY_POINT", (unsigned) phdri[0].p_paddr);
    linker->defineSymbol("PHYSICAL_START", (unsigned) phdri[0].p_paddr);
}

unsigned PackVmlinuxARMEL::write_vmlinux_head(
    OutputFile *fo,
    Shdr *stxt
)
{
    // First word from vmlinux-head.S
    fo->write(&stub_arm_v5a_linux_kernel_vmlinux_head[0], 4);

    // Second word
    TE32 tmp_u32;
    unsigned const t = (0xff000000 &
            BeLePolicy::get32(&stub_arm_v5a_linux_kernel_vmlinux_head[4]))
        | (0x00ffffff & (0u - 1 + ((3+ ph.c_len)>>2)));
    tmp_u32 = t;
    fo->write(&tmp_u32, 4);

    stxt->sh_addralign = 4;
    stxt->sh_size += sizeof(stub_arm_v5a_linux_kernel_vmlinux_head);

    return sizeof(stub_arm_v5a_linux_kernel_vmlinux_head);
}

unsigned PackVmlinuxARMEB::write_vmlinux_head(
    OutputFile *fo,
    Shdr *stxt
)
{
    // First word from vmlinux-head.S
    fo->write(&stub_armeb_v5a_linux_kernel_vmlinux_head[0], 4);

    // Second word
    TE32 tmp_u32;
    unsigned const t = (0xff000000 &
            BeLePolicy::get32(&stub_armeb_v5a_linux_kernel_vmlinux_head[4]))
        | (0x00ffffff & (0u - 1 + ((3+ ph.c_len)>>2)));
    tmp_u32 = t;
    fo->write(&tmp_u32, 4);

    stxt->sh_addralign = 4;
    stxt->sh_size += sizeof(stub_armeb_v5a_linux_kernel_vmlinux_head);

    return sizeof(stub_armeb_v5a_linux_kernel_vmlinux_head);
}

unsigned PackVmlinuxPPC32::write_vmlinux_head(
    OutputFile * /*fo*/,
    Shdr * /*stxt*/
)
{
    return 0;
}

unsigned PackVmlinuxPPC64LE::write_vmlinux_head(
    OutputFile * /*const fo*/,
    Shdr * /*const stxt*/
)
{
    return 0;
}


bool PackVmlinuxARMEL::has_valid_vmlinux_head()
{
    TE32 buf[2];
    fi->seek(p_text->sh_offset + sizeof(stub_arm_v5a_linux_kernel_vmlinux_head) -8, SEEK_SET);
    fi->readx(buf, sizeof(buf));
    //unsigned const word0 = buf[0];
    unsigned const word1 = buf[1];
    if (0xeb==(word1>>24)
    &&  (0x00ffffff& word1)==(0u - 1 + ((3+ ph.c_len)>>2))) {
        return true;
    }
    return false;
}

bool PackVmlinuxARMEB::has_valid_vmlinux_head()
{
    TE32 buf[2];
    fi->seek(p_text->sh_offset + sizeof(stub_armeb_v5a_linux_kernel_vmlinux_head) -8, SEEK_SET);
    fi->readx(buf, sizeof(buf));
    //unsigned const word0 = buf[0];
    unsigned const word1 = buf[1];
    if (0xeb==(word1>>24)
    &&  (0x00ffffff& word1)==(0u - 1 + ((3+ ph.c_len)>>2))) {
        return true;
    }
    return false;
}

bool PackVmlinuxPPC32::has_valid_vmlinux_head()
{
    TE32 buf[2];
    fi->seek(p_text->sh_offset + sizeof(stub_powerpc_linux_kernel_vmlinux_head) -8, SEEK_SET);
    fi->readx(buf, sizeof(buf));
    //unsigned const word0 = buf[0];
    unsigned const word1 = buf[1];
    if (0xeb==(word1>>24)
    &&  (0x00ffffff& word1)==(0u - 1 + ((3+ ph.c_len)>>2))) {
        return true;
    }
    return false;
}

static const CLANG_FORMAT_DUMMY_STATEMENT
#include "stub/powerpc64le-linux.kernel.vmlinux-head.h"
bool PackVmlinuxPPC64LE::has_valid_vmlinux_head()
{
    TE64 buf[2];
    fi->seek(p_text->sh_offset + sizeof(stub_powerpc64le_linux_kernel_vmlinux_head) -8, SEEK_SET);
    fi->readx(buf, sizeof(buf));
    //unsigned const word0 = buf[0];
    unsigned const word1 = buf[1];
    if (0xeb==(word1>>24)
    &&  (0x00ffffff& word1)==(0u - 1 + ((3+ ph.c_len)>>2))) {
        return true;
    }
    return false;
}

bool PackVmlinuxI386::has_valid_vmlinux_head()
{
    unsigned char buf[5];
    fi->seek(p_text->sh_offset + sizeof(stub_i386_linux_kernel_vmlinux_head) -5, SEEK_SET);
    fi->readx(&buf[0], 5);
    if (0xE8!=buf[0] ||  BeLePolicy::get32(&buf[1]) != ph.c_len)
    {
        return false;
    }
    return true;
}

bool PackVmlinuxAMD64::has_valid_vmlinux_head()
{
    unsigned char buf[5];
    fi->seek(p_text->sh_offset + sizeof(stub_amd64_linux_kernel_vmlinux_head) -5, SEEK_SET);
    fi->readx(&buf[0], 5);
    if (0xE8!=buf[0] ||  BeLePolicy::get32(&buf[1]) != ph.c_len)
    {
        return false;
    }
    return true;
}


/*************************************************************************
//
**************************************************************************/

const int *PackVmlinuxAMD64::getCompressionMethods(int method, int level) const
{
    return Packer::getDefaultCompressionMethods_le32(method, level);
}


const int *PackVmlinuxAMD64::getFilters() const
{
    static const int filters[] = {
        0x49, 0x46,
    -1 };
    return filters;
}

bool PackVmlinuxAMD64::is_valid_e_entry(Addr e_entry)
{
    return 0x200000<=e_entry; // 2 MiB
}

Linker* PackVmlinuxAMD64::newLinker() const
{
    return new ElfLinkerX86;
}



// instantiate instances
template class PackVmlinuxBase<ElfClass_BE32>;
// template class PackVmlinuxBase<ElfClass_BE64>; // currently not used
template class PackVmlinuxBase<ElfClass_LE32>;
template class PackVmlinuxBase<ElfClass_LE64>;

/* vim:set ts=4 sw=4 et: */
