Files
s390-tools/zfcpdump/zfcpdump_part.c
Alexander Egorenkov 802c7db50e zfcpdump: fix hsa release for V!=R kernel
Since V!=R kernel introduction, HSA memory can be
contained in/spread over more than one ELF LOAD segment of
/proc/vmcore simultaneously. Therefore, the old HSA release logic is no
longer valid because it assumes that HSA memory is fully contained in
exactly one ELF LOAD segment of /proc/vmcore. This resulted in zfcpdump
releasing HSA memory too soon and by that making parts of /proc/vmcore
which cover HSA memory unreadable by user space. To correct this
problem on V!=R kernel, we need first to find all ELF LOAD segments
containing HSA memory, write those /proc/vmcore parts first and only then
release HSA memory. The new HSA release logic must be able to handle
both V!=R and V==R kernels to be backwards compatible.

====================
Tests of V!=R kernel
====================

Output of test run (KASLR on)
=============================

Writing dump:

TRACE: Read: /sys/kernel/debug/zcore/hsa:
TRACE: '2ffff000'

TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000001cb0000 p_paddr=0x00000002f1e30000 p_vaddr=0x000002c609044000
TRACE: ELF LOAD segment: p_offset=0x0000000001cb9000 p_filesz=0x0000000040000000 p_paddr=0x0000000000000000 p_vaddr=0x000001bd00000000
TRACE: ELF LOAD segment: p_offset=0x0000000041cb9000 p_filesz=0x0000000300000000 p_paddr=0x0000000100000000 p_vaddr=0x000001be00000000

TRACE: Write copy table entry 0: off=0x0000000001cb9000 size=0x000000002ffff000 hsa=1
TRACE: Write copy table entry 1: off=0x0000000000001000 size=0x0000000001cb8000 hsa=0
TRACE: Release HSA memory
TRACE: Write copy table entry 2: off=0x0000000031cb8000 size=0x0000000310001000 hsa=0
TRACE: Write copy table entry 3: off=0x0000000000000000 size=0x0000000000001000 hsa=0

Dump successful

Output of test run (KASLR off)
==============================

Writing dump:

TRACE: Read: /sys/kernel/debug/zcore/hsa:
TRACE: '2ffff000'

TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000001cb0000 p_paddr=0x0000000000c21000 p_vaddr=0x000003ffe0000000
TRACE: ELF LOAD segment: p_offset=0x0000000001cb9000 p_filesz=0x0000000040000000 p_paddr=0x0000000000000000 p_vaddr=0x000002f200000000
TRACE: ELF LOAD segment: p_offset=0x0000000041cb9000 p_filesz=0x0000000300000000 p_paddr=0x0000000100000000 p_vaddr=0x000002f300000000

TRACE: Write copy table entry 0: off=0x0000000000009000 size=0x0000000001cb0000 hsa=1
TRACE: Write copy table entry 1: off=0x0000000001cb9000 size=0x000000002ffff000 hsa=1
TRACE: Write copy table entry 2: off=0x0000000000001000 size=0x0000000000008000 hsa=0
TRACE: Release HSA memory
TRACE: Write copy table entry 3: off=0x0000000031cb8000 size=0x0000000310001000 hsa=0
TRACE: Write copy table entry 4: off=0x0000000000000000 size=0x0000000000001000 hsa=0

Dump successful

====================
Tests of V==R kernel
====================

Output of test run (KASLR on)
=============================

Writing dump:

TRACE: Read: /sys/kernel/debug/zcore/hsa:
TRACE: '2ffff000'

TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000000000000 p_paddr=0x0000000000000000 p_vaddr=0x0000000000000000
TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000040000000 p_paddr=0x0000000000000000 p_vaddr=0x0000000000000000
TRACE: ELF LOAD segment: p_offset=0x0000000040009000 p_filesz=0x0000000300000000 p_paddr=0x0000000100000000 p_vaddr=0x0000000100000000

TRACE: Write copy table entry 0: off=0x0000000000009000 size=0x000000002ffff000 hsa=1
TRACE: Write copy table entry 1: off=0x0000000000001000 size=0x0000000000008000 hsa=0
TRACE: Release HSA memory
TRACE: Write copy table entry 2: off=0x0000000030008000 size=0x0000000310001000 hsa=0
TRACE: Write copy table entry 3: off=0x0000000000000000 size=0x0000000000001000 hsa=0

Dump successful

Output of test run (KASLR off)
==============================

Writing dump:

TRACE: Read: /sys/kernel/debug/zcore/hsa:
TRACE: '2ffff000'

TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000000000000 p_paddr=0x0000000000000000 p_vaddr=0x0000000000000000
TRACE: ELF LOAD segment: p_offset=0x0000000000009000 p_filesz=0x0000000040000000 p_paddr=0x0000000000000000 p_vaddr=0x0000000000000000
TRACE: ELF LOAD segment: p_offset=0x0000000040009000 p_filesz=0x0000000300000000 p_paddr=0x0000000100000000 p_vaddr=0x0000000100000000

TRACE: Write copy table entry 0: off=0x0000000000009000 size=0x000000002ffff000 hsa=1
TRACE: Write copy table entry 1: off=0x0000000000001000 size=0x0000000000008000 hsa=0
TRACE: Release HSA memory
TRACE: Write copy table entry 2: off=0x0000000030008000 size=0x0000000310001000 hsa=0
TRACE: Write copy table entry 3: off=0x0000000000000000 size=0x0000000000001000 hsa=0

Dump successful

Signed-off-by: Alexander Egorenkov <egorenar@linux.ibm.com>
Reviewed-by: Mikhail Zaslonko <zaslonko@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2024-05-27 16:49:54 +02:00

513 lines
14 KiB
C

/*
* zfcpdump - Write /proc/vmcore to SCSI partition
*
* This tool should be used in an intitramfs together with a kernel with
* enabled CONFIG_ZFCPDUMP kernel build option. The tool is able to write
* standalone system dumps on SCSI disks.
*
* See Documentation/s390/zfcpdump.txt for more information!
*
* Copyright IBM Corp. 2003, 2017
*
* 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 <asm/types.h>
#include <ctype.h>
#include <dirent.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/hdreg.h>
#include <linux/reboot.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "lib/util_base.h"
#include "lib/zt_common.h"
#include "boot/boot_defs.h"
#include "zfcpdump.h"
#define COPY_BUF_SIZE 0x10000UL
/*
* Copy table entry
*/
struct copy_table_entry {
unsigned long size;
unsigned long off;
bool hsa;
};
struct copy_table {
int cnt;
int max;
struct copy_table_entry *entry;
};
/*
* Globals
*/
static struct scsi_dump_sb dump_sb;
static struct scsi_mbr mbr;
/*
* Read file at given offset
*/
static int pread_file(const char *path, char *buf, int size, uint64_t off)
{
int fd;
PRINT_TRACE("Read: %s:\n", path);
fd = open(path, O_RDONLY);
if (fd == -1) {
PRINT_PERR("open %s failed\n", path);
return -1;
}
if (lseek(fd, off, SEEK_SET) < 0) {
PRINT_PERR("seek %s offset %llu failed\n", path,
(unsigned long long) off);
return -1;
}
if (read(fd, buf, size) < 0) {
PRINT_PERR("read %s failed\n", path);
close(fd);
return -1;
}
close(fd);
return 0;
}
/*
* Create checksum for buffer
*/
static inline uint32_t csum_partial(const void *buf, int len, uint32_t sum)
{
register unsigned long reg2 asm("2") = (unsigned long) buf;
register unsigned long reg3 asm("3") = (unsigned long) len;
asm volatile(
"0: cksm %0,%1\n" /* do checksum on longs */
" jo 0b\n"
: "+d" (sum), "+d" (reg2), "+d" (reg3) : : "cc",
"memory");
return sum;
}
/*
* Create checksum on SCSI device
*/
static int csum_get(uint64_t off, uint64_t len, uint64_t *result)
{
char buf[len];
if (pread_file(DEV_SCSI, (char *)&buf, sizeof(buf), off) < 0) {
PRINT_ERR("Error reading checksum from disk\n");
return -1;
}
*result = (uint64_t)csum_partial(&buf, len, SCSI_DUMP_SB_SEED);
PRINT_TRACE("Got crc %llx\n", (unsigned long long) *result);
return 0;
}
/*
* Update superblock checksum on SCSI disk
*/
static int csum_update(int fd)
{
/* Write crc into zfcpdump header */
if (csum_get(dump_sb.part_start + dump_sb.csum_offset,
dump_sb.csum_size, &dump_sb.csum)) {
PRINT_ERR("Get check sum failed\n");
return -1;
}
if (lseek(fd, mbr.boot_info.bp.dump.param.scsi.block, SEEK_SET) < 0) {
PRINT_PERR("Seek failed\n");
return -1;
}
if (write(fd, &dump_sb, sizeof(dump_sb)) < 0) {
PRINT_PERR("Write failed\n");
return -1;
}
return 0;
}
static int cmp_ct_entries(const void *_entry1, const void *_entry2)
{
const struct copy_table_entry *entry1 = (const struct copy_table_entry *)_entry1;
const struct copy_table_entry *entry2 = (const struct copy_table_entry *)_entry2;
/* Sort copy table entries by their file offset in ascending order */
if (entry1->off <= entry2->off)
return -1;
else if (entry1->off > entry2->off)
return 1;
else
return 0;
}
static inline void copy_table_add_entry(struct copy_table *table, unsigned long off,
unsigned long size, bool hsa)
{
const int i = table->cnt;
table->entry[i].off = off;
table->entry[i].size = size;
table->entry[i].hsa = hsa;
table->cnt++;
}
static void copy_table_add_non_hsa_file_regions(struct copy_table *table)
{
const int hsa_entry_count = table->cnt;
unsigned long off, size;
int i;
/*
* We write the front page of /proc/vmcore at the end of the dump processing.
* This ensures that the dump stays invalid until all data
* is written. It is guaranteed that a copy table entry for file offset 0
* never covers HSA memory and at least of size of a single page because
* HSA memory is always page aligned.
*/
off = PAGE_SIZE;
size = table->entry[0].off - off;
if (size > 0)
copy_table_add_entry(table, off, size, false);
/*
* Add copy table entries covering non-HSA file regions located before
* each copy table entry covering a HSA file region. Start with the second
* HSA copy table entry.
*/
for (i = 1; i < hsa_entry_count; i++) {
off = table->entry[i - 1].off + table->entry[i - 1].size;
size = table->entry[i].off - off;
if (size > 0)
copy_table_add_entry(table, off, size, false);
}
/*
* Add a copy table entry that covers the end of /proc/vmcore which is
* not covered by a copy table entry for HSA.
*/
i = hsa_entry_count - 1;
off = table->entry[i].off + table->entry[i].size;
if (off < g.vmcore_size) {
size = g.vmcore_size - off;
copy_table_add_entry(table, off, size, false);
}
/*
* Add a copy table entry covering the front page of /proc/vmcore which
* is not covered by HSA as the last entry.
*/
copy_table_add_entry(table, 0, PAGE_SIZE, false);
}
static int copy_table_init(int fd, struct copy_table *table)
{
const unsigned long hsa_size = get_hsa_size();
unsigned long off, size;
int i, max_table_size;
Elf64_Ehdr ehdr;
Elf64_Phdr phdr;
g.vmcore_size = lseek(fd, (off_t) 0, SEEK_END);
lseek(fd, 0L, SEEK_SET);
if (g.vmcore_size < sizeof(ehdr))
return -1;
if (read(fd, &ehdr, sizeof(ehdr)) < 0)
return -1;
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0)
return -1;
if (ehdr.e_type != ET_CORE)
return -1;
if (ehdr.e_machine != EM_S390 || ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
PRINT_ERR("Only 64 bit core dump files are supported\n");
return -1;
}
/*
* Each ELF LOAD segment may contain at most one HSA file region which will
* result in exactly one HSA copy table entry. Furthermore, this will result
* in at most 1 extra non-HSA copy table entry preceding the HSA copy
* table entry apart from the first and the last HSA copy table entries
* which will result in 2 non-HSA copy table entries.
*
* /proc/vmcore
* -------------------------------------------------------------------
* | page sized | non-HSA | HSA | non-HSA | HSA | non-HSA |
* | non-HSA | region 2 | region 1 | region 3 | region 2 | region 4 |
* | region 1 | | | | | |
* -------------------------------------------------------------------
*/
table->cnt = 0;
table->max = ehdr.e_phnum * 2 + 2;
max_table_size = table->max * sizeof(struct copy_table_entry);
table->entry = malloc(max_table_size);
if (!table->entry) {
PRINT_ERR("Memory allocation of %d byte(s) failed\n", max_table_size);
return -1;
}
/*
* First add all HSA file regions to copy table.
* Each ELF LOAD segment may contain at most one HSA segment.
*/
for (i = 0; i < ehdr.e_phnum; i++) {
if (read(fd, &phdr, sizeof(phdr)) < 0)
return -1;
if (phdr.p_type != PT_LOAD)
continue;
PRINT_TRACE("ELF LOAD segment: p_offset=0x%016lx p_filesz=0x%016lx p_paddr=0x%016lx p_vaddr=0x%016lx\n",
phdr.p_offset, phdr.p_filesz, phdr.p_paddr, phdr.p_vaddr);
if (phdr.p_paddr >= hsa_size)
continue;
off = phdr.p_offset;
size = MIN(phdr.p_filesz, hsa_size - phdr.p_paddr);
if (size > 0)
copy_table_add_entry(table, off, size, true);
}
if (table->cnt == 0) {
PRINT_ERR("Could not find ELF LOAD segments containing HSA\n");
return -1;
}
/* Sort all HSA copy table entries by their file offset */
qsort(table->entry, table->cnt, sizeof(struct copy_table_entry), cmp_ct_entries);
/*
* Add copy table entries which cover all of /proc/vmcore not covered
* by HSA copy table entries added above.
*/
copy_table_add_non_hsa_file_regions(table);
return 0;
}
/*
* Copy one copy table entry form /proc/vmcore to dump partition
*/
static int copy_table_entry_write(int fdin, int fdout,
const struct copy_table_entry *entry,
unsigned long offset)
{
unsigned long buf_size, bytes_left, off;
void *map;
if (entry->size == 0)
return 0;
off = entry->off;
bytes_left = entry->size;
if (lseek(fdout, entry->off + offset, SEEK_SET) < 0)
return -1;
while (bytes_left > 0) {
buf_size = MIN(COPY_BUF_SIZE, bytes_left);
map = mmap(0, buf_size, PROT_READ, MAP_SHARED, fdin, off);
if (map == (void *)-1) {
PRINT_PERR("Mapping failed\n");
return -1;
}
if (write(fdout, map, buf_size) < 0) {
PRINT_PERR("Write to partition failed\n");
return -1;
}
munmap(map, buf_size);
bytes_left -= buf_size;
off += buf_size;
show_progress(buf_size);
}
return 0;
}
static int copy_dump(const char *in, const char *out, unsigned long offset)
{
struct copy_table table = { 0 };
char busy_str[] = "zfcpdump busy";
int fdout, fdin, i, rc = -1, hsa_released = 0;
fdin = open(in, O_RDONLY);
if (fdin < 0) {
PRINT_ERR("Open %s failed\n", in);
return -1;
}
g.vmcore_size = lseek(fdin, (off_t) 0, SEEK_END);
lseek(fdin, 0L, SEEK_SET);
if (g.vmcore_size > dump_sb.dump_size) {
PRINT_ERR("Disk too small: dump=%lldMB (diskspace=%lldMB)\n",
TO_MIB(g.vmcore_size), TO_MIB(dump_sb.dump_size));
goto out_close_fdin;
}
fdout = open(out, O_WRONLY);
if (fdout < 0) {
PRINT_ERR("Open %s failed\n", out);
goto out_close_fdin;
}
/* Overwrite old header */
if (lseek(fdout, offset, SEEK_SET) < 0)
goto out_close_fdin;
if (write(fdout, busy_str, sizeof(busy_str)) == -1)
goto out_close_fdin;
if (csum_update(fdout))
goto out_close_fdin;
if (copy_table_init(fdin, &table))
goto out_close_fdin;
show_progress(0);
for (i = 0; i < table.cnt; i++) {
PRINT_TRACE("Write copy table entry %d: off=0x%016lx size=0x%016lx hsa=%d\n",
i, table.entry[i].off, table.entry[i].size, table.entry[i].hsa ? 1 : 0);
if (!hsa_released && !table.entry[i].hsa) {
/*
* First encountered non-HSA copy table entry guarantees
* that no more HSA memory copy table entries will appear
* and, therefore, HSA memory can be finally released.
*/
PRINT_TRACE("Release HSA memory\n");
release_hsa();
hsa_released = 1;
}
if (copy_table_entry_write(fdin, fdout, &table.entry[i], offset))
goto out_close_fdout;
}
rc = 0;
out_close_fdout:
if (csum_update(fdout))
rc = -1;
fsync(fdout);
close(fdout);
free(table.entry);
out_close_fdin:
if (!hsa_released)
release_hsa();
close(fdin);
return rc;
}
/*
* Finds the matching partition to a given start and end. If a matching
* partition is found, the partition number is returned.
*/
int find_part_num(uint64_t start, uint64_t size)
{
struct hd_geometry geo;
uint32_t block_size;
uint64_t part_size;
char path[11];
int fd, i;
PRINT_TRACE("Partiton to dump start: 0x%llx end: 0x%llx\n",
(unsigned long long) start, (unsigned long long) size);
for (i = 1; i < 16; i++) {
snprintf(path, sizeof(path), DEV_SCSI "%d", i);
fd = open(path, O_RDONLY);
if (fd == -1)
continue;
if (ioctl(fd, HDIO_GETGEO, &geo) != 0) {
PRINT_PERR("Could not retrieve partition"
" geometry information\n");
return -1;
}
if (ioctl(fd, BLKGETSIZE64, &part_size)) {
PRINT_PERR("Could not retrieve partition"
" size information\n");
return -1;
}
if (ioctl(fd, BLKSSZGET, &block_size)) {
PRINT_PERR("Could not get blocksize");
return -1;
}
PRINT_TRACE("Partiton %s start: 0x%llx end: 0x%llx\n", path,
(unsigned long long) geo.start * block_size,
(unsigned long long) part_size);
if ((start == geo.start * block_size) && (size == part_size))
return i;
}
return -1;
}
/*
* Read the on-disk zfcpdump boot info and superblock into global variables
*/
static int get_scsi_dump_params(void)
{
uint64_t csum;
int part_num;
if (pread_file(DEV_SCSI, (char *)&mbr, sizeof(mbr), 0) < 0) {
PRINT_ERR("Cannot read MBR\n");
return -1;
}
if (memcmp(&mbr.boot_info.magic, BOOT_INFO_MAGIC,
sizeof(mbr.boot_info.magic))) {
PRINT_ERR("Boot_Info wrong magic\n");
return -1;
}
if (mbr.boot_info.dev_type != BOOT_INFO_DEV_TYPE_SCSI) {
PRINT_ERR("Boot_Info wrong dev type: %d\n",
mbr.boot_info.dev_type);
return -1;
}
if (mbr.boot_info.bp_type != BOOT_INFO_BP_TYPE_DUMP) {
PRINT_ERR("Boot_Info wrong bp type: %d\n",
mbr.boot_info.bp_type);
return -1;
}
if (mbr.boot_info.version != BOOT_INFO_VERSION) {
PRINT_ERR("Boot_Info wrong version: %d\n",
mbr.boot_info.version);
return -1;
}
if (pread_file(DEV_SCSI, (char *)&dump_sb, sizeof(dump_sb),
mbr.boot_info.bp.dump.param.scsi.block) < 0) {
PRINT_ERR("Cannot read superblock\n");
return -1;
}
if (dump_sb.magic != SCSI_DUMP_SB_MAGIC) {
PRINT_ERR("Dump data block wrong magic\n");
return -1;
}
part_num = find_part_num(dump_sb.part_start, dump_sb.part_size);
if (part_num < 0) {
PRINT_ERR("Specified dump partition not found\n");
return -1;
}
if (csum_get(dump_sb.part_start + dump_sb.csum_offset,
dump_sb.csum_size, &csum)) {
PRINT_ERR("Getting Checksum failed\n");
return -1;
}
if (csum != dump_sb.csum) {
PRINT_ERR("Checksum wrong, filesystem changed\n");
return -1;
}
PRINT(" partition: " DEV_SCSI "%d\n", part_num);
return 0;
}
/*
* Main routine of the zfcpdump tool
*/
int main(int UNUSED(argc), char *UNUSED(argv[]))
{
int rc;
if (zfcpdump_init())
return terminate(1);
PRINT("Dump parameters:\n");
PRINT(" devno....: %s\n", g.dump_devno);
PRINT(" wwpn.....: %s\n", g.dump_wwpn);
PRINT(" lun......: %s\n", g.dump_lun);
PRINT(" conf.....: %s\n", g.dump_bootprog);
if (get_scsi_dump_params())
return terminate(1);
print_newline();
PRINT("Writing dump:\n");
rc = copy_dump("/proc/vmcore", DEV_SCSI,
dump_sb.part_start + dump_sb.dump_offset);
return terminate(rc);
}