/*
 * Elf format, (pfn, gmfn) table, IA64 support.
 * Copyright (c) 2007 Isaku Yamahata <yamahata at valinux co jp>
 *                    VA Linux Systems Japan K.K.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * xen dump-core file format follows ELF format specification.
 * Analisys tools shouldn't depends on the order of sections.
 * They should follow elf header and check section names.
 *
 *  +--------------------------------------------------------+
 *  |ELF header                                              |
 *  +--------------------------------------------------------+
 *  |section headers                                         |
 *  |    null section header                                 |
 *  |    .shstrtab                                           |
 *  |    .note.Xen                                           |
 *  |    .xen_prstatus                                       |
 *  |    .xen_shared_info if present                         |
 *  |    .xen_pages                                          |
 *  |    .xen_p2m or .xen_pfn                                |
 *  +--------------------------------------------------------+
 *  |.note.Xen:note section                                  |
 *  |    "Xen" is used as note name,                         |
 *  |    types are defined in xen/include/public/elfnote.h   |
 *  |    and descriptors are defined in xc_core.h.           |
 *  |    dumpcore none                                       |
 *  |    dumpcore header                                     |
 *  |    dumpcore xen version                                |
 *  |    dumpcore format version                             |
 *  +--------------------------------------------------------+
 *  |.xen_prstatus                                           |
 *  |       vcpu_guest_context_t[nr_vcpus]                   |
 *  +--------------------------------------------------------+
 *  |.xen_shared_info if possible                            |
 *  +--------------------------------------------------------+
 *  |.xen_pages                                              |
 *  |    page * nr_pages                                     |
 *  +--------------------------------------------------------+
 *  |.xen_p2m or .xen_pfn                                    |
 *  |    .xen_p2m: struct xen_dumpcore_p2m[nr_pages]         |
 *  |    .xen_pfn: uint64_t[nr_pages]                        |
 *  +--------------------------------------------------------+
 *  |.shstrtab: section header string table                  |
 *  +--------------------------------------------------------+
 *
 */

#include "xc_private.h"
#include "xg_core.h"
#include <stdlib.h>
#include <unistd.h>

#include <xen/libelf/libelf.h>

/* number of pages to write at a time */
#define DUMP_INCREMENT (4 * 1024)

/* string table */
struct xc_core_strtab {
    char       *strings;
    uint16_t    length;
    uint16_t    max;
};

static struct xc_core_strtab*
xc_core_strtab_init(xc_interface *xch)
{
    struct xc_core_strtab *strtab;
    char *strings;
    strtab = malloc(sizeof(*strtab));
    if ( strtab == NULL )
        return NULL;

    strings = malloc(PAGE_SIZE);
    if ( strings == NULL )
    {
        PERROR("Could not allocate string table init");
        free(strtab);
        return NULL;
    }
    strtab->strings = strings;
    strtab->max = PAGE_SIZE;

    /* index 0 represents none */
    strtab->strings[0] = '\0';
    strtab->length = 1;

    return strtab;
}

static void
xc_core_strtab_free(struct xc_core_strtab *strtab)
{
    free(strtab->strings);
    free(strtab);
}

static uint16_t
xc_core_strtab_get(xc_interface *xch, struct xc_core_strtab *strtab, const char *name)
{
    uint16_t ret = 0;
    uint16_t len = strlen(name) + 1;

    if ( strtab->length > UINT16_MAX - len )
    {
        PERROR("too long string table");
        errno = E2BIG;
        return ret;
    }
    
    if ( strtab->length + len > strtab->max )
    {
        char *tmp;
        if ( strtab->max > UINT16_MAX / 2 )
        {
            PERROR("too long string table");
            errno = ENOMEM;
            return ret;
        }

        tmp = realloc(strtab->strings, strtab->max * 2);
        if ( tmp == NULL )
        {
            PERROR("Could not allocate string table");
            return ret;
        }

        strtab->strings = tmp;
        strtab->max *= 2;
    }

    ret = strtab->length;
    strcpy(strtab->strings + strtab->length, name);
    strtab->length += len;
    return ret;
}


/* section headers */
struct xc_core_section_headers {
    uint16_t    num;
    uint16_t    num_max;

    Elf64_Shdr  *shdrs;
};
#define SHDR_INIT       ((uint16_t)16)
#define SHDR_INC        ((uint16_t)4)

static struct xc_core_section_headers*
xc_core_shdr_init(xc_interface *xch)
{
    struct xc_core_section_headers *sheaders;
    sheaders = malloc(sizeof(*sheaders));
    if ( sheaders == NULL )
        return NULL;

    sheaders->num = 0;
    sheaders->num_max = SHDR_INIT;
    sheaders->shdrs = malloc(sizeof(sheaders->shdrs[0]) * sheaders->num_max);
    if ( sheaders->shdrs == NULL )
    {
        free(sheaders);
        return NULL;
    }
    return sheaders;
}

static void
xc_core_shdr_free(struct xc_core_section_headers *sheaders)
{
    free(sheaders->shdrs);
    free(sheaders);
}

Elf64_Shdr*
xc_core_shdr_get(xc_interface *xch,
                 struct xc_core_section_headers *sheaders)
{
    Elf64_Shdr *shdr;

    if ( sheaders->num == sheaders->num_max )
    {
        Elf64_Shdr *shdrs;
        if ( sheaders->num_max > UINT16_MAX - SHDR_INC )
        {
            errno = E2BIG;
            return NULL;
        }
        sheaders->num_max += SHDR_INC;
        shdrs = realloc(sheaders->shdrs,
                        sizeof(sheaders->shdrs[0]) * sheaders->num_max);
        if ( shdrs == NULL )
            return NULL;
        sheaders->shdrs = shdrs;
    }

    shdr = &sheaders->shdrs[sheaders->num];
    sheaders->num++;
    memset(shdr, 0, sizeof(*shdr));
    return shdr;
}

int
xc_core_shdr_set(xc_interface *xch,
                 Elf64_Shdr *shdr,
                 struct xc_core_strtab *strtab,
                 const char *name, uint32_t type,
                 uint64_t offset, uint64_t size,
                 uint64_t addralign, uint64_t entsize)
{
    uint64_t name_idx = xc_core_strtab_get(xch, strtab, name);
    if ( name_idx == 0 )
        return -1;

    shdr->sh_name = name_idx;
    shdr->sh_type = type;
    shdr->sh_offset = offset;
    shdr->sh_size = size;
    shdr->sh_addralign = addralign;
    shdr->sh_entsize = entsize;
    return 0;
}

static void
xc_core_ehdr_init(Elf64_Ehdr *ehdr)
{
    memset(ehdr, 0, sizeof(*ehdr));
    ehdr->e_ident[EI_MAG0] = ELFMAG0;
    ehdr->e_ident[EI_MAG1] = ELFMAG1;
    ehdr->e_ident[EI_MAG2] = ELFMAG2;
    ehdr->e_ident[EI_MAG3] = ELFMAG3;
    ehdr->e_ident[EI_CLASS] = ELFCLASS64;
    ehdr->e_ident[EI_DATA] = ELF_ARCH_DATA;
    ehdr->e_ident[EI_VERSION] = EV_CURRENT;
    ehdr->e_ident[EI_OSABI] = ELFOSABI_SYSV;
    ehdr->e_ident[EI_ABIVERSION] = EV_CURRENT;

    ehdr->e_type = ET_CORE;
    /* e_machine will be filled in later */
    ehdr->e_version = EV_CURRENT;
    ehdr->e_entry = 0;
    ehdr->e_phoff = 0;
    ehdr->e_shoff = sizeof(*ehdr);
    ehdr->e_flags = ELF_CORE_EFLAGS;
    ehdr->e_ehsize = sizeof(*ehdr);
    ehdr->e_phentsize = sizeof(Elf64_Phdr);
    ehdr->e_phnum = 0;
    ehdr->e_shentsize = sizeof(Elf64_Shdr);
    /* ehdr->e_shnum and ehdr->e_shstrndx aren't known here yet.
     * fill it later */
}

static int
elfnote_fill_xen_version(xc_interface *xch,
                         struct xen_dumpcore_elfnote_xen_version_desc
                         *xen_version)
{
    int rc;
    memset(xen_version, 0, sizeof(*xen_version));

    rc = xc_version(xch, XENVER_version, NULL);
    if ( rc < 0 )
        return rc;
    xen_version->major_version = rc >> 16;
    xen_version->minor_version = rc & ((1 << 16) - 1);

    rc = xc_version(xch, XENVER_extraversion,
                    &xen_version->extra_version);
    if ( rc < 0 )
        return rc;

    rc = xc_version(xch, XENVER_compile_info,
                    &xen_version->compile_info);
    if ( rc < 0 )
        return rc;

    rc = xc_version(xch,
                    XENVER_capabilities, &xen_version->capabilities);
    if ( rc < 0 )
        return rc;

    rc = xc_version(xch, XENVER_changeset, &xen_version->changeset);
    if ( rc < 0 )
        return rc;

    rc = xc_version(xch, XENVER_platform_parameters,
                    &xen_version->platform_parameters);
    if ( rc < 0 )
        return rc;

    rc = xc_version(xch, XENVER_pagesize, NULL);
    if ( rc < 0 )
        return rc;
    xen_version->pagesize = rc;

    return 0;
}

static void
elfnote_fill_format_version(struct xen_dumpcore_elfnote_format_version_desc
                            *format_version)
{
    format_version->version = XEN_DUMPCORE_FORMAT_VERSION_CURRENT;
}

static void
elfnote_init(struct elfnote *elfnote)
{
    /* elf note section */
    memset(elfnote, 0, sizeof(*elfnote));
    elfnote->namesz = strlen(XEN_DUMPCORE_ELFNOTE_NAME) + 1;
    strncpy(elfnote->name, XEN_DUMPCORE_ELFNOTE_NAME, sizeof(elfnote->name));
}

static int
elfnote_dump_none(xc_interface *xch, void *args, dumpcore_rtn_t dump_rtn)
{
    int sts;
    struct elfnote elfnote;
    struct xen_dumpcore_elfnote_none_desc none;

    elfnote_init(&elfnote);
    /* Avoid compile warning about constant-zero-sized memset(). */
    /*memset(&none, 0, sizeof(none));*/

    elfnote.descsz = sizeof(none);
    elfnote.type = XEN_ELFNOTE_DUMPCORE_NONE;
    sts = dump_rtn(xch, args, (char*)&elfnote, sizeof(elfnote));
    if ( sts != 0 )
        return sts;
    return dump_rtn(xch, args, (char*)&none, sizeof(none));
}

static int
elfnote_dump_core_header(
    xc_interface *xch,
    void *args, dumpcore_rtn_t dump_rtn, const xc_domaininfo_t *info,
    int nr_vcpus, unsigned long nr_pages)
{
    int sts;
    struct elfnote elfnote;
    struct xen_dumpcore_elfnote_header_desc header;
    
    elfnote_init(&elfnote);
    memset(&header, 0, sizeof(header));
    
    elfnote.descsz = sizeof(header);
    elfnote.type = XEN_ELFNOTE_DUMPCORE_HEADER;
    header.xch_magic = (info->flags & XEN_DOMINF_hvm_guest) ? XC_CORE_MAGIC_HVM
                                                            : XC_CORE_MAGIC;
    header.xch_nr_vcpus = nr_vcpus;
    header.xch_nr_pages = nr_pages;
    header.xch_page_size = PAGE_SIZE;
    sts = dump_rtn(xch, args, (char*)&elfnote, sizeof(elfnote));
    if ( sts != 0 )
        return sts;
    return dump_rtn(xch, args, (char*)&header, sizeof(header));
}

static int
elfnote_dump_xen_version(xc_interface *xch, void *args,
                         dumpcore_rtn_t dump_rtn, unsigned int guest_width)
{
    int sts;
    struct elfnote elfnote;
    struct xen_dumpcore_elfnote_xen_version_desc xen_version;

    elfnote_init(&elfnote);
    memset(&xen_version, 0, sizeof(xen_version));

    elfnote.descsz = sizeof(xen_version);
    elfnote.type = XEN_ELFNOTE_DUMPCORE_XEN_VERSION;
    elfnote_fill_xen_version(xch, &xen_version);
    if (guest_width < sizeof(unsigned long))
    {
        // 32 bit elf file format differs in pagesize's alignment
        char *p = (char *)&xen_version.pagesize;
        memmove(p - 4, p, sizeof(xen_version.pagesize));
    }
    sts = dump_rtn(xch, args, (char*)&elfnote, sizeof(elfnote));
    if ( sts != 0 )
        return sts;
    return dump_rtn(xch, args, (char*)&xen_version, sizeof(xen_version));
}

static int
elfnote_dump_format_version(xc_interface *xch,
                            void *args, dumpcore_rtn_t dump_rtn)
{
    int sts;
    struct elfnote elfnote;
    struct xen_dumpcore_elfnote_format_version_desc format_version;

    elfnote_init(&elfnote);
    memset(&format_version, 0, sizeof(format_version));
    
    elfnote.descsz = sizeof(format_version);
    elfnote.type = XEN_ELFNOTE_DUMPCORE_FORMAT_VERSION;
    elfnote_fill_format_version(&format_version);
    sts = dump_rtn(xch, args, (char*)&elfnote, sizeof(elfnote));
    if ( sts != 0 )
        return sts;
    return dump_rtn(xch, args, (char*)&format_version, sizeof(format_version));
}

int
xc_domain_dumpcore_via_callback(xc_interface *xch,
                                uint32_t domid,
                                void *args,
                                dumpcore_rtn_t dump_rtn)
{
    xc_domaininfo_t info;
    shared_info_any_t *live_shinfo = NULL;
    struct domain_info_context _dinfo = {};
    struct domain_info_context *dinfo = &_dinfo;

    int nr_vcpus = 0;
    char *dump_mem, *dump_mem_start = NULL;
    vcpu_guest_context_any_t *ctxt = NULL;
    struct xc_core_arch_context arch_ctxt;
    char dummy[PAGE_SIZE];
    int dummy_len;
    int sts = -1;

    unsigned long i;
    unsigned long j;
    unsigned long nr_pages;
    unsigned long max_mfn;

    xc_core_memory_map_t *memory_map = NULL;
    unsigned int nr_memory_map;
    unsigned int map_idx;

    int auto_translated_physmap;
    xen_pfn_t *p2m = NULL;
    struct xen_dumpcore_p2m *p2m_array = NULL;

    uint64_t *pfn_array = NULL;

    Elf64_Ehdr ehdr;
    uint64_t filesz;
    uint64_t offset;
    uint64_t fixup;

    struct xc_core_strtab *strtab = NULL;
    uint16_t strtab_idx;
    struct xc_core_section_headers *sheaders = NULL;
    Elf64_Shdr *shdr;
 
    xc_core_arch_context_init(&arch_ctxt);
    if ( (dump_mem_start = malloc(DUMP_INCREMENT*PAGE_SIZE)) == NULL )
    {
        PERROR("Could not allocate dump_mem");
        goto out;
    }

    if ( xc_domain_getinfo_single(xch, domid, &info) < 0 )
    {
        PERROR("Could not get info for dom%u", domid);
        goto out;
    }
    /* Map the shared info frame */
    live_shinfo = xc_map_foreign_range(xch, domid, PAGE_SIZE,
                                       PROT_READ, info.shared_info_frame);
    if ( !live_shinfo && !(info.flags & XEN_DOMINF_hvm_guest) )
    {
        PERROR("Couldn't map live_shinfo");
        goto out;
    }
    auto_translated_physmap = xc_core_arch_auto_translated_physmap(&info);

    if ( !auto_translated_physmap )

    {
        if ( xc_domain_get_guest_width(xch, domid, &dinfo->guest_width) != 0 )
        {
            PERROR("Could not get address size for domain");
            goto out;
        }
    }
    else
    {
        /*
         * Autotranslated guest never sets guest width in the first
         * place. Force guest_width to be sizeof(unsigned long) so
         * code below functions properly.
         *
         * Here is why this is correct.
         *
         * 1. Before f969bc9fc, xc_domain_get_guest_width for HVM (x86
         * and ARM) always returned hypervisor's idea of
         * sizeof(unsigned long).
         *
         * 2. There has never been a situation in which hypervisor's
         * word width is smaller than toolstack domain's (i.e. no
         * 32bit hypervisor + 64bit toolstack).
         *
         * Predicates in code test guest_width against toolstack
         * domain's sizeof(unsigned long), so setting guest_width to
         * toolstack domain's idea of sizeof(unsigned long) matches
         * the original behaviour for HVM guests.
         */
        dinfo->guest_width = sizeof(unsigned long);
    }

    ctxt = calloc(sizeof(*ctxt), info.max_vcpu_id + 1);
    if ( !ctxt )
    {
        PERROR("Could not allocate vcpu context array");
        goto out;
    }

    for ( i = 0; i <= info.max_vcpu_id; i++ )
    {
        if ( xc_vcpu_getcontext(xch, domid, i, &ctxt[nr_vcpus]) == 0 )
        {
            if ( xc_core_arch_context_get(&arch_ctxt, &ctxt[nr_vcpus],
                                          xch, domid) )
                continue;
            nr_vcpus++;
        }
    }
    if ( nr_vcpus == 0 )
    {
        PERROR("No VCPU context could be grabbed");
        goto out;
    }

    /* obtain memory map */
    sts = xc_core_arch_memory_map_get(xch, &arch_ctxt, &info,
                                      live_shinfo, &memory_map,
                                      &nr_memory_map);
    if ( sts != 0 )
        goto out;

    /*
     * Note: this is the *current* number of pages and may change under
     * a live dump-core.  We'll just take this value, and if more pages
     * exist, we'll skip them.  If there's less, then we'll just not use
     * all the array...
     *
     * We don't want to use the total potential size of the memory map
     * since that is usually much higher than info.tot_pages.
     */
    nr_pages = info.tot_pages;

    if ( !auto_translated_physmap )
    {
        /* obtain p2m table */
        p2m_array = malloc(nr_pages * sizeof(p2m_array[0]));
        if ( p2m_array == NULL )
        {
            PERROR("Could not allocate p2m array");
            goto out;
        }

        sts = xc_core_arch_map_p2m(xch, dinfo, &info, live_shinfo, &p2m);
        if ( sts != 0 )
            goto out;

        sts = xc_maximum_ram_page(xch, &max_mfn);
        if ( sts != 0 )
            goto out;
    }
    else
    {
        pfn_array = malloc(nr_pages * sizeof(pfn_array[0]));
        if ( pfn_array == NULL )
        {
            PERROR("Could not allocate pfn array");
            goto out;
        }
    }

    /* ehdr.e_shnum and ehdr.e_shstrndx aren't known here yet. fill it later*/
    xc_core_ehdr_init(&ehdr);

    /* create section header */
    strtab = xc_core_strtab_init(xch);
    if ( strtab == NULL )
    {
        PERROR("Could not allocate string table");
        goto out;
    }
    sheaders = xc_core_shdr_init(xch);
    if ( sheaders == NULL )
    {
        PERROR("Could not allocate section headers");
        goto out;
    }
    /* null section */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("Could not get section header for null section");
        goto out;
    }

    /* .shstrtab */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("Could not get section header for shstrtab");
        goto out;
    }
    strtab_idx = shdr - sheaders->shdrs;
    /* strtab_shdr.sh_offset, strtab_shdr.sh_size aren't unknown.
     * fill it later
     */
    sts = xc_core_shdr_set(xch, shdr, strtab, ELF_SHSTRTAB, SHT_STRTAB, 0, 0, 0, 0);
    if ( sts != 0 )
        goto out;

    /* elf note section */
    /* here the number of section header is unknown. fix up offset later. */
    offset = sizeof(ehdr);
    filesz =
        sizeof(struct xen_dumpcore_elfnote_none) +         /* none */
        sizeof(struct xen_dumpcore_elfnote_header) +       /* core header */
        sizeof(struct xen_dumpcore_elfnote_xen_version) +  /* xen version */
        sizeof(struct xen_dumpcore_elfnote_format_version);/* format version */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("Could not get section header for note section");
        goto out;
    }
    sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_NOTE, SHT_NOTE,
                           offset, filesz, 0, 0);
    if ( sts != 0 )
        goto out;
    offset += filesz;

    /* prstatus */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("Could not get section header for .xen_prstatus");
        goto out;
    }
    filesz = sizeof(*ctxt) * nr_vcpus;
    sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_PRSTATUS,
                           SHT_PROGBITS, offset, filesz,
                           __alignof__(*ctxt), sizeof(*ctxt));
    if ( sts != 0 )
        goto out;
    offset += filesz;

    /* arch context */
    sts = xc_core_arch_context_get_shdr(xch, &arch_ctxt, sheaders, strtab,
                                        &filesz, offset);
    if ( sts != 0 )
        goto out;
    offset += filesz;

    /* shared_info */
    if ( live_shinfo != NULL )
    {
        shdr = xc_core_shdr_get(xch,sheaders);
        if ( shdr == NULL )
        {
            PERROR("Could not get section header for .xen_shared_info");
            goto out;
        }
        filesz = PAGE_SIZE;
        sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_SHARED_INFO,
                               SHT_PROGBITS, offset, filesz,
                               __alignof__(*live_shinfo), PAGE_SIZE);
        if ( sts != 0 )
            goto out;
        offset += filesz;
    }

    /*
     * pages and p2m/pfn are the last section to allocate section headers
     * so that we know the number of section headers here.
     * 2 = pages section and p2m/pfn table section
     */
    fixup = (sheaders->num + 2) * sizeof(*shdr);
    /* zeroth section should have zero offset */
    for ( i = 1; i < sheaders->num; i++ )
        sheaders->shdrs[i].sh_offset += fixup;
    offset += fixup;
    dummy_len = ROUNDUP(offset, PAGE_SIZE) - offset; /* padding length */
    offset += dummy_len;

    /* pages */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("could not get section headers for .xen_pages");
        goto out;
    }
    filesz = (uint64_t)nr_pages * PAGE_SIZE;
    sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_PAGES, SHT_PROGBITS,
                           offset, filesz, PAGE_SIZE, PAGE_SIZE);
    if ( sts != 0 )
        goto out;
    offset += filesz;

    /* p2m/pfn table */
    shdr = xc_core_shdr_get(xch,sheaders);
    if ( shdr == NULL )
    {
        PERROR("Could not get section header for .xen_{p2m, pfn} table");
        goto out;
    }
    if ( !auto_translated_physmap )
    {
        filesz = (uint64_t)nr_pages * sizeof(p2m_array[0]);
        sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_P2M,
                               SHT_PROGBITS,
                               offset, filesz, __alignof__(p2m_array[0]),
                               sizeof(p2m_array[0]));
    }
    else
    {
        filesz = (uint64_t)nr_pages * sizeof(pfn_array[0]);
        sts = xc_core_shdr_set(xch, shdr, strtab, XEN_DUMPCORE_SEC_PFN,
                               SHT_PROGBITS,
                               offset, filesz, __alignof__(pfn_array[0]),
                               sizeof(pfn_array[0]));
    }
    if ( sts != 0 )
        goto out;
    offset += filesz;

    /* fixing up section header string table section header */
    filesz = strtab->length;
    sheaders->shdrs[strtab_idx].sh_offset = offset;
    sheaders->shdrs[strtab_idx].sh_size = filesz;

    /* write out elf header */
    ehdr.e_shnum = sheaders->num;
    ehdr.e_shstrndx = strtab_idx;
    ehdr.e_machine = ELF_ARCH_MACHINE;
    sts = dump_rtn(xch, args, (char*)&ehdr, sizeof(ehdr));
    if ( sts != 0 )
        goto out;

    /* section headers */
    sts = dump_rtn(xch, args, (char*)sheaders->shdrs,
                   sheaders->num * sizeof(sheaders->shdrs[0]));
    if ( sts != 0 )
        goto out;

    /* elf note section: xen core header */
    sts = elfnote_dump_none(xch, args, dump_rtn);
    if ( sts != 0 )
        goto out;

    /* elf note section: xen core header */
    sts = elfnote_dump_core_header(xch, args, dump_rtn, &info, nr_vcpus, nr_pages);
    if ( sts != 0 )
        goto out;

    /* elf note section: xen version */
    sts = elfnote_dump_xen_version(xch, args, dump_rtn, dinfo->guest_width);
    if ( sts != 0 )
        goto out;

    /* elf note section: format version */
    sts = elfnote_dump_format_version(xch, args, dump_rtn);
    if ( sts != 0 )
        goto out;

    /* prstatus: .xen_prstatus */
    sts = dump_rtn(xch, args, (char *)ctxt, sizeof(*ctxt) * nr_vcpus);
    if ( sts != 0 )
        goto out;

    if ( live_shinfo != NULL )
    {
        /* shared_info: .xen_shared_info */
        sts = dump_rtn(xch, args, (char*)live_shinfo, PAGE_SIZE);
        if ( sts != 0 )
            goto out;
    }

    /* arch specific context */
    sts = xc_core_arch_context_dump(xch, &arch_ctxt, args, dump_rtn);
    if ( sts != 0 )
        goto out;

    /* Pad the output data to page alignment. */
    memset(dummy, 0, PAGE_SIZE);
    sts = dump_rtn(xch, args, dummy, dummy_len);
    if ( sts != 0 )
        goto out;

    /* dump pages: .xen_pages */
    j = 0;
    dump_mem = dump_mem_start;
    for ( map_idx = 0; map_idx < nr_memory_map; map_idx++ )
    {
        uint64_t pfn_start;
        uint64_t pfn_end;

        pfn_start = memory_map[map_idx].addr >> PAGE_SHIFT;
        pfn_end = pfn_start + (memory_map[map_idx].size >> PAGE_SHIFT);
        for ( i = pfn_start; i < pfn_end; i++ )
        {
            uint64_t gmfn;
            void *vaddr;

            if ( !auto_translated_physmap )
            {
                if ( i >= dinfo->p2m_size )
                    break;

                if ( dinfo->guest_width >= sizeof(unsigned long) )
                {
                    if ( dinfo->guest_width == sizeof(unsigned long) )
                        gmfn = p2m[i];
                    else
                        gmfn = ((uint64_t *)p2m)[i];
                    if ( gmfn == INVALID_PFN )
                        continue;
                }
                else
                {
                    gmfn = ((uint32_t *)p2m)[i];
                    if ( gmfn == (uint32_t)INVALID_PFN )
                       continue;
                }
                if ( gmfn > max_mfn )
                    continue;

                if ( j >= nr_pages )
                {
                    j++;
                    continue;
                }

                p2m_array[j].pfn = i;
                p2m_array[j].gmfn = gmfn;
            }
            else
            {
                if ( !xc_core_arch_gpfn_may_present(&arch_ctxt, i) )
                    continue;

                if ( j >= nr_pages )
                {
                    j++;
                    continue;
                }

                gmfn = i;
                pfn_array[j] = i;
            }

            vaddr = xc_map_foreign_range(
                xch, domid, PAGE_SIZE, PROT_READ, gmfn);
            if ( vaddr == NULL )
                continue;
            memcpy(dump_mem, vaddr, PAGE_SIZE);
            munmap(vaddr, PAGE_SIZE);
            dump_mem += PAGE_SIZE;
            if ( (j + 1) % DUMP_INCREMENT == 0 )
            {
                sts = dump_rtn(
                    xch, args, dump_mem_start, dump_mem - dump_mem_start);
                if ( sts != 0 )
                    goto out;
                dump_mem = dump_mem_start;
            }

            j++;
        }
    }

    if ( j > nr_pages )
    {
        /*
         * When live dump-mode (-L option) is specified,
         * guest domain may increase memory.
         */
        IPRINTF("exceeded nr_pages (%ld) losing %ld pages", nr_pages, j - nr_pages);
    }

    sts = dump_rtn(xch, args, dump_mem_start, dump_mem - dump_mem_start);
    if ( sts != 0 )
        goto out;
    if ( j < nr_pages )
    {
        /* When live dump-mode (-L option) is specified,
         * guest domain may reduce memory. pad with zero pages.
         */
        DPRINTF("j (%ld) != nr_pages (%ld)", j, nr_pages);
        memset(dump_mem_start, 0, PAGE_SIZE);
        for (; j < nr_pages; j++) {
            sts = dump_rtn(xch, args, dump_mem_start, PAGE_SIZE);
            if ( sts != 0 )
                goto out;
            if ( !auto_translated_physmap )
            {
                p2m_array[j].pfn = XC_CORE_INVALID_PFN;
                p2m_array[j].gmfn = XC_CORE_INVALID_GMFN;
            }
            else
                pfn_array[j] = XC_CORE_INVALID_PFN;
        }
    }

    /* p2m/pfn table: .xen_p2m/.xen_pfn */
    if ( !auto_translated_physmap )
        sts = dump_rtn(
            xch, args, (char *)p2m_array, sizeof(p2m_array[0]) * nr_pages);
    else
        sts = dump_rtn(
            xch, args, (char *)pfn_array, sizeof(pfn_array[0]) * nr_pages);
    if ( sts != 0 )
        goto out;

    /* elf section header string table: .shstrtab */
    sts = dump_rtn(xch, args, strtab->strings, strtab->length);
    if ( sts != 0 )
        goto out;

    sts = 0;

out:
    if ( memory_map != NULL )
        free(memory_map);
    if ( p2m != NULL )
        munmap(p2m, PAGE_SIZE * dinfo->p2m_frames);
    if ( p2m_array != NULL )
        free(p2m_array);
    if ( pfn_array != NULL )
        free(pfn_array);
    if ( sheaders != NULL )
        xc_core_shdr_free(sheaders);
    if ( strtab != NULL )
        xc_core_strtab_free(strtab);
    if ( ctxt != NULL )
        free(ctxt);
    if ( dump_mem_start != NULL )
        free(dump_mem_start);
    if ( live_shinfo != NULL )
        munmap(live_shinfo, PAGE_SIZE);
    xc_core_arch_context_free(&arch_ctxt);

    return sts;
}

/* Callback args for writing to a local dump file. */
struct dump_args {
    int     fd;
};

/* Callback routine for writing to a local dump file. */
static int local_file_dump(xc_interface *xch,
                           void *args, char *buffer, unsigned int length)
{
    struct dump_args *da = args;

    if ( write_exact(da->fd, buffer, length) == -1 )
    {
        PERROR("Failed to write buffer");
        return -errno;
    }

    if ( length >= (DUMP_INCREMENT * PAGE_SIZE) )
    {
        // Now dumping pages -- make sure we discard clean pages from
        // the cache after each write
        discard_file_cache(xch, da->fd, 0 /* no flush */);
    }

    return 0;
}

int
xc_domain_dumpcore(xc_interface *xch,
                   uint32_t domid,
                   const char *corename)
{
    struct dump_args da;
    int sts;

    if ( (da.fd = open(corename, O_CREAT|O_RDWR|O_TRUNC, S_IWUSR|S_IRUSR)) < 0 )
    {
        PERROR("Could not open corefile %s", corename);
        return -errno;
    }

    sts = xc_domain_dumpcore_via_callback(
        xch, domid, &da, &local_file_dump);

    /* flush and discard any remaining portion of the file from cache */
    discard_file_cache(xch, da.fd, 1/* flush first*/);

    close(da.fd);

    return sts;
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
