Files
s390-tools/zdump/dfi_elf.c
Alexander Egorenkov 4043137cd7 zdump: Fix handling of PT_LOAD segments with p_filesz < p_memsz in ELF DFI
According to the ELF specification, the file size of a PT_LOAD
memory segment might be less than its memory size. In that case,
the remaining bytes must be filled with zeros. The ELF DFI implementation
didn't handle this case correctly. Such ELF core files might be produced
by the makedumpfile tool.

From elf.5 man page:
------------------------------------------------------
PT_LOAD
       The array element specifies a loadable segment,
       described by p_filesz and p_memsz.  The bytes
       from the file are mapped to the beginning of the
       memory segment.  If the segment's memory size
       p_memsz is larger than the file size p_filesz,
       the "extra" bytes are defined to hold the value
       0 and to follow the segment's initialized area.
       The file size may not be larger than the memory
       size.  Loadable segment entries in the program
       header table appear in ascending order, sorted
       on the p_vaddr member.

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2021-09-30 17:21:32 +02:00

340 lines
7.1 KiB
C

/*
* zgetdump - Tool for copying and converting System z dumps
*
* ELF core dump input format
*
* Copyright IBM Corp. 2001, 2018
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <assert.h>
#include <elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "lib/util_libc.h"
#include "zgetdump.h"
/*
* Read memory for given memory chunk
*/
static void dfi_elf_mem_chunk_read_fn(struct dfi_mem_chunk *mem_chunk, u64 off,
void *buf, u64 cnt)
{
u64 elf_load_off = *((u64 *) mem_chunk->data);
zg_seek(g.fh, elf_load_off + off, ZG_CHECK);
zg_read(g.fh, buf, cnt, ZG_CHECK);
}
/*
* Add load (memory chunk) to DFI dump
*/
static int pt_load_add(Elf64_Phdr *phdr)
{
u64 *off_ptr;
if (phdr->p_paddr != phdr->p_vaddr) {
phdr->p_paddr = phdr->p_vaddr;
STDERR("Dump file \"%s\" is a user space core dump\n",
g.opts.device);
}
if (phdr->p_memsz == 0)
return -EINVAL;
if (phdr->p_offset + phdr->p_filesz > zg_size(g.fh))
return -EINVAL;
if (phdr->p_filesz > phdr->p_memsz)
return -EINVAL;
if (phdr->p_filesz > 0) {
off_ptr = zg_alloc(sizeof(*off_ptr));
*off_ptr = phdr->p_offset;
dfi_mem_chunk_add(phdr->p_paddr, phdr->p_filesz, off_ptr,
dfi_elf_mem_chunk_read_fn, zg_free);
}
if (phdr->p_memsz - phdr->p_filesz > 0) {
/* Add zero memory chunk */
dfi_mem_chunk_add(phdr->p_paddr + phdr->p_filesz,
phdr->p_memsz - phdr->p_filesz, NULL,
dfi_mem_chunk_read_zero, NULL);
}
return 0;
}
/*
* Skip name of note
*/
static void nt_name_skip(Elf64_Nhdr *note)
{
zg_seek_cur(g.fh, ROUNDUP(note->n_namesz, 4), ZG_CHECK);
}
/*
* Read note
*/
static int nt_read(Elf64_Nhdr *note, void *buf)
{
off_t buf_len = ROUNDUP(note->n_descsz, 4);
char tmp_buf[buf_len];
nt_name_skip(note);
if (zg_read(g.fh, tmp_buf, buf_len, ZG_CHECK_ERR) != buf_len)
return -EINVAL;
if (buf)
memcpy(buf, tmp_buf, note->n_descsz);
return 0;
}
/*
* Skip note
*/
static int nt_skip(Elf64_Nhdr *note)
{
return nt_read(note, NULL);
}
/*
* Ensure that CPU is already defined by prstatus note
*/
static void check_cpu(struct dfi_cpu *cpu, const char *note_str)
{
if (cpu)
return;
ERR_EXIT("Invalid ELF dump (%s before prstatus found)", note_str);
}
/*
* Read prstatus note and return new DFI CPU
*/
static struct dfi_cpu *nt_prstatus_read(Elf64_Nhdr *note)
{
struct dfi_cpu *cpu = dfi_cpu_alloc();
struct nt_prstatus_64 nt_prstatus;
if (nt_read(note, &nt_prstatus))
return NULL;
memcpy(cpu->gprs, &nt_prstatus.gprs, sizeof(cpu->gprs));
memcpy(cpu->psw, &nt_prstatus.psw, sizeof(cpu->psw));
memcpy(cpu->acrs, &nt_prstatus.acrs, sizeof(cpu->acrs));
dfi_cpu_add(cpu);
return cpu;
}
/*
* Read fpregset note
*/
static int nt_fpregset_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
struct nt_fpregset_64 nt_fpregset;
check_cpu(cpu, "FPREGSET");
if (nt_read(note, &nt_fpregset))
return -EINVAL;
memcpy(&cpu->fpc, &nt_fpregset.fpc, sizeof(cpu->fpc));
memcpy(cpu->fprs, &nt_fpregset.fprs, sizeof(cpu->fprs));
return 0;
}
/*
* Read s390 timer note
*/
static int nt_s390_timer_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TIMER");
return nt_read(note, &cpu->timer);
}
/*
* Read s390 todcmp note
*/
static int nt_s390_todcmp_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TODCMP");
return nt_read(note, &cpu->todcmp);
}
/*
* Read s390 todpreg note
*/
static int nt_s390_todpreg_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_TODPREG");
return nt_read(note, &cpu->todpreg);
}
/*
* Read s390 ctrs note
*/
static int nt_s390_ctrs_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_CTRS");
return nt_read(note, &cpu->ctrs);
}
/*
* Read s390 prefix note
*/
static int nt_s390_prefix_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_PREFIX");
return nt_read(note, &cpu->prefix);
}
/*
* Read s390 vxrs_low note
*/
static int nt_s390_vxrs_low_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_VXRS_LOW");
return nt_read(note, &cpu->vxrs_low);
}
/*
* Read s390 vxrs_high note
*/
static int nt_s390_vxrs_high_read(struct dfi_cpu *cpu, Elf64_Nhdr *note)
{
check_cpu(cpu, "S390_VXRS_HIGH");
return nt_read(note, &cpu->vxrs_high);
}
/*
* Add all notes for notes phdr
*/
static int pt_notes_add(Elf64_Phdr *phdr)
{
struct dfi_cpu *cpu_current = NULL;
u64 notes_start_off;
Elf64_Nhdr note;
int rc;
zg_seek(g.fh, phdr->p_offset, ZG_CHECK);
notes_start_off = zg_tell(g.fh, ZG_CHECK);
while (zg_tell(g.fh, ZG_CHECK) - notes_start_off < phdr->p_filesz) {
rc = zg_read(g.fh, &note, sizeof(note), ZG_CHECK_ERR);
if (rc != sizeof(note))
return -EINVAL;
switch (note.n_type) {
case NT_PRSTATUS:
cpu_current = nt_prstatus_read(&note);
if (!cpu_current)
return -EINVAL;
break;
case NT_FPREGSET:
if (nt_fpregset_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TIMER:
if (nt_s390_timer_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TODCMP:
if (nt_s390_todcmp_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_TODPREG:
if (nt_s390_todpreg_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_CTRS:
if (nt_s390_ctrs_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_PREFIX:
if (nt_s390_prefix_read(cpu_current, &note))
return -EINVAL;
break;
case NT_S390_VXRS_LOW:
if (nt_s390_vxrs_low_read(cpu_current, &note))
return -EINVAL;
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
break;
case NT_S390_VXRS_HIGH:
if (nt_s390_vxrs_high_read(cpu_current, &note))
return -EINVAL;
dfi_cpu_content_fac_add(DFI_CPU_CONTENT_FAC_VX);
break;
default:
if (nt_skip(&note))
return -EINVAL;
break;
}
}
return 0;
}
/*
* Read ELF header
*/
static int read_elf_hdr(Elf64_Ehdr *ehdr)
{
if (zg_size(g.fh) < sizeof(*ehdr))
return -ENODEV;
zg_read(g.fh, ehdr, sizeof(*ehdr), ZG_CHECK);
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0)
return -ENODEV;
if (ehdr->e_type != ET_CORE)
return -ENODEV;
if (ehdr->e_machine != EM_S390 || ehdr->e_ident[EI_CLASS] != ELFCLASS64)
ERR_EXIT("Only s390x (64 bit) core dump files are supported");
return 0;
}
/*
* Initialize ELF input dump format
*/
static int dfi_elf_init(void)
{
Elf64_Ehdr ehdr;
Elf64_Phdr *phdr;
int i;
if (read_elf_hdr(&ehdr) != 0)
return -ENODEV;
df_elf_ensure_s390x();
dfi_arch_set(DFI_ARCH_64);
dfi_cpu_info_init(DFI_CPU_CONTENT_ALL);
phdr = util_malloc(sizeof(*phdr) * ehdr.e_phnum);
zg_seek(g.fh, ehdr.e_phoff, ZG_CHECK);
zg_read(g.fh, phdr, sizeof(*phdr) * ehdr.e_phnum, ZG_CHECK);
for (i = 0; i < ehdr.e_phnum; i++) {
switch (phdr[i].p_type) {
case PT_LOAD:
if (pt_load_add(&phdr[i])) {
free(phdr);
return -EINVAL;
}
break;
case PT_NOTE:
if (pt_notes_add(&phdr[i])) {
free(phdr);
return -EINVAL;
}
break;
default:
break;
}
}
free(phdr);
dfi_attr_version_set(ehdr.e_ident[EI_VERSION]);
return 0;
}
/*
* ELF DFI operations
*/
struct dfi dfi_elf = {
.name = "elf",
.init = dfi_elf_init,
.feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK,
};