CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
840 lines
22 KiB
C
840 lines
22 KiB
C
/* coremerge.c -- merge AMDGPU corefiles.
|
|
|
|
Copyright (C) 2022-2024 Free Software Foundation, Inc.
|
|
Copyright (C) 2022-2024 Advanced Micro Devices, Inc. All rights reserved.
|
|
|
|
This file is part of GNU Binutils.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, 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; if not, write to the Free Software
|
|
Foundation, 51 Franklin Street - Fifth Floor, Boston,
|
|
MA 02110-1301, USA. */
|
|
|
|
|
|
/* coremerge overview.
|
|
|
|
coremerge is used to merge a AMDGPU core file (generated by the ROCm
|
|
runtime) and the CPU core file generated for the same process and merge
|
|
them into an unified core file GDB can load. */
|
|
|
|
#include "sysdep.h"
|
|
#include "libiberty.h"
|
|
#include "getopt.h"
|
|
#include "bfd.h"
|
|
#include "elf-bfd.h"
|
|
#include "bucomm.h"
|
|
#include <sys/types.h>
|
|
#include <stdbool.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include "elf/external.h"
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#define verbose_printf(fmt, ...) \
|
|
do { \
|
|
if (verbose) \
|
|
printf (fmt, ##__VA_ARGS__); \
|
|
} while (0) \
|
|
|
|
#define is_power_of_two(x) ((x) && ((x) & ((x) - 1)) == 0)
|
|
|
|
/* When generating sparse cores, we skip writing blocks comprised of all zeros
|
|
if the block is of size of SPARSE_BLOCK_SIZE and aligned on a
|
|
SPARSE_BLOCK_SIZE boundary in the underlying output file. */
|
|
#define SPARSE_BLOCK_SIZE 0x1000
|
|
|
|
/* Largest amount of data (in bytes) we copy from an input to the produced
|
|
combined core file. */
|
|
#define COPY_SIZE (256 * SPARSE_BLOCK_SIZE)
|
|
|
|
/* Set by command line flags. */
|
|
|
|
static int show_version = 0;
|
|
static int verbose = 0;
|
|
static int force = 0;
|
|
|
|
/* Print help message to STREAM and exit the process with exit code STATUS. */
|
|
|
|
static void usage (FILE *, int) ATTRIBUTE_NORETURN;
|
|
static void
|
|
usage (FILE *stream, int status)
|
|
{
|
|
fprintf (stream, _("Usage: %s <option(s)> combined-corefile "
|
|
"host-corefile gpu-corefile\n"),
|
|
program_name);
|
|
fprintf (stream,
|
|
_(" Merge the two core dump files host-corefile and gpu-corefile "
|
|
"and produce combined-corefile.\n"));
|
|
|
|
fprintf (stream, _("\
|
|
-f, --force Overwrite the output file if it already exists\n"));
|
|
fprintf (stream, _("\
|
|
@<file> Read options from <file>\n"));
|
|
fprintf (stream, _("\
|
|
-h, --help Display this information\n"));
|
|
fprintf (stream, _("\
|
|
-v, --version Display this program's version number\n"));
|
|
fprintf (stream, _("\
|
|
-V, --verbose Display detailed operation\n"));
|
|
|
|
if (REPORT_BUGS_TO[0] && status == 0)
|
|
fprintf (stream, _("Report bugs to %s.\n"), REPORT_BUGS_TO);
|
|
exit (status);
|
|
}
|
|
|
|
static struct option long_options[]=
|
|
{
|
|
{"force", no_argument, &force, 1},
|
|
{"verbose", no_argument, &verbose, 1},
|
|
{"version", no_argument, &show_version, 1},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{NULL, no_argument, NULL, 0}
|
|
};
|
|
|
|
/* Read notes from the segment referenced by PHDR in IBFD and append their
|
|
content to NOTES_DATA which will be the content of the note segment added
|
|
to OBFD. NOTES_DATA_SIZE gives the size of the buffer pointed by
|
|
NOTES_DATA.
|
|
|
|
Return 0 on success, non-0 if an error occurred.
|
|
|
|
This is based on bfd/elf.c:elf_parse_notes. */
|
|
|
|
static int
|
|
gather_notes (bfd *obfd, bfd *ibfd, Elf_Internal_Phdr *phdr,
|
|
char **notes_data, int *notes_data_size)
|
|
{
|
|
void *note_data, *note_end, *curr;
|
|
Elf_External_Note *nh;
|
|
Elf_Internal_Note in;
|
|
int align = phdr->p_align;
|
|
|
|
assert (notes_data != NULL);
|
|
assert (notes_data_size != NULL);
|
|
assert (phdr != NULL && phdr->p_type == PT_NOTE);
|
|
|
|
if (align < 4)
|
|
align = 4;
|
|
if (align != 4 && align != 8)
|
|
return -1;
|
|
|
|
note_data = xmalloc (phdr->p_filesz);
|
|
|
|
if (bfd_seek (ibfd, phdr->p_offset, SEEK_SET) != 0)
|
|
{
|
|
free (note_data);
|
|
return -1;
|
|
}
|
|
|
|
if (bfd_read (note_data, phdr->p_filesz, ibfd) != phdr->p_filesz)
|
|
{
|
|
free (note_data);
|
|
return -1;
|
|
}
|
|
|
|
note_end = ((char *) note_data) + phdr->p_filesz;
|
|
curr = note_data;
|
|
while (curr < note_end)
|
|
{
|
|
nh = (Elf_External_Note *) curr;
|
|
|
|
in.type = H_GET_32 (ibfd, nh->type);
|
|
in.namesz = H_GET_32 (ibfd, nh->namesz);
|
|
in.namedata = nh->name;
|
|
|
|
if (in.namedata + in.namesz > (char *) note_end)
|
|
{
|
|
non_fatal (_("Error decoding notes from %s."),
|
|
bfd_get_filename (ibfd));
|
|
free (note_data);
|
|
return -1;
|
|
}
|
|
|
|
in.descsz = H_GET_32 (ibfd, nh->descsz);
|
|
in.descdata
|
|
= ((char *) curr) + ELF_NOTE_DESC_OFFSET (in.namesz, align);
|
|
if (in.descsz != 0
|
|
&& (in.descdata > (char *) note_end
|
|
|| in.descdata + in.descsz > (char *) note_end))
|
|
{
|
|
non_fatal (_("Error decoding notes from %s."),
|
|
bfd_get_filename (ibfd));
|
|
free (note_data);
|
|
return -1;
|
|
}
|
|
|
|
verbose_printf (" Found note %s[type=0x%lx]\n", in.namedata, in.type);
|
|
|
|
*notes_data = elfcore_write_note (obfd, *notes_data,
|
|
notes_data_size, in.namedata,
|
|
in.type, in.descdata,
|
|
in.descsz);
|
|
|
|
curr
|
|
= (((char *) curr)
|
|
+ ELF_NOTE_NEXT_OFFSET (in.namesz, in.descsz, align));
|
|
}
|
|
|
|
free (note_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Describe load sections which should be created in the output BFD and keep
|
|
track of where the actual data should be loaded from. */
|
|
|
|
struct out_section
|
|
{
|
|
/* The BFD the data comes from. */
|
|
bfd *ibfd;
|
|
/* The BFD the data will be copied to. */
|
|
bfd *obfd;
|
|
/* The Phdr describing where the data lives in IBFD. */
|
|
Elf_Internal_Phdr *iphdr;
|
|
/* The section in OBFD where the data will be copied. Can be NULL if no
|
|
data needs to be copied to the OBFD (content-less section). */
|
|
asection *osection;
|
|
/* Next section to be processed. */
|
|
struct out_section *next;
|
|
};
|
|
|
|
/* Check if the address ADDR is contained in the segment PHDR.
|
|
|
|
Helper function for gather_load_segments. */
|
|
|
|
static inline int
|
|
phdr_contains (Elf_Internal_Phdr *phdr, bfd_vma addr)
|
|
{
|
|
return phdr->p_vaddr <= addr && addr < phdr->p_vaddr + phdr->p_memsz;
|
|
}
|
|
|
|
/* Record the load segment described by PHDR in the list of segments to create
|
|
in the output core file.
|
|
|
|
It happens that the same memory region (i.e. segment with same load address
|
|
and memory size) exists in both the host and gpu core. This is because the
|
|
kernel creates empty (i.e. with a size of 0 on disk, so 0-initialized when
|
|
loaded by the debugger) segments for the regions where the GPU memory is
|
|
mapped onto the host process. For such segments, make sure that we only
|
|
keep the segment which has actual data.
|
|
|
|
Return 0 on success, non-0 otherwise. */
|
|
|
|
static int
|
|
gather_load_segments (bfd *ibfd, Elf_Internal_Phdr *phdr,
|
|
struct out_section **descr)
|
|
{
|
|
struct out_section *osec_dsc;
|
|
|
|
assert (phdr != NULL && phdr->p_type == PT_LOAD);
|
|
assert (descr != NULL);
|
|
|
|
/* Check if we already have an entry for the same load address but no actual
|
|
data. If so, replace it with the current segment. */
|
|
|
|
for (struct out_section *head = *descr; head != NULL; head = head->next)
|
|
{
|
|
if (head->iphdr->p_vaddr == phdr->p_vaddr && head->iphdr->p_filesz == 0
|
|
&& head->iphdr->p_memsz == phdr->p_memsz)
|
|
{
|
|
verbose_printf (" Overriding segment loaded at %#lx with one from "
|
|
"%s\n",
|
|
head->iphdr->p_vaddr, bfd_get_filename (ibfd));
|
|
head->ibfd = ibfd;
|
|
head->iphdr = phdr;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (phdr_contains (head->iphdr, phdr->p_vaddr)
|
|
|| phdr_contains (head->iphdr, phdr->p_vaddr + phdr->p_memsz - 1)
|
|
|| phdr_contains (phdr, head->iphdr->p_vaddr)
|
|
|| phdr_contains (phdr,
|
|
head->iphdr->p_vaddr + head->iphdr->p_memsz - 1))
|
|
non_fatal (_("[warning] Found overapping segments."));
|
|
}
|
|
|
|
/* Record the current section to be loaded. */
|
|
verbose_printf (" Using segment loaded at %#lx from %s\n",
|
|
phdr->p_vaddr, bfd_get_filename (ibfd));
|
|
osec_dsc = XCNEW (struct out_section);
|
|
|
|
osec_dsc->ibfd = ibfd;
|
|
osec_dsc->iphdr = phdr;
|
|
|
|
/* Link the new descriptor in the list. */
|
|
osec_dsc->next = *descr;
|
|
*descr = osec_dsc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Returns 1 if all SIZE first bytes from the buffer referenced by BUF are
|
|
all NUL bytes, 0 otherwise.
|
|
|
|
SIZE must be at most SPARSE_BLOCK_SIZE. */
|
|
|
|
static int
|
|
is_all_zeros (const bfd_byte *buf, size_t size)
|
|
{
|
|
assert (size <= SPARSE_BLOCK_SIZE);
|
|
static const bfd_byte zeros[SPARSE_BLOCK_SIZE] = {0};
|
|
return memcmp (buf, zeros, size) == 0;
|
|
}
|
|
|
|
/* Wrapper around bfd_set_section_contents that skips writing blocks of
|
|
SPARSE_BLOCK_SIZE size if such block only contains 0s.
|
|
|
|
The caller ensures that blocks should be written on a SPARSE_BLOCK_SIZE
|
|
boundary on the underlying file for this to effectively produce sparse
|
|
core dumps. */
|
|
|
|
static int
|
|
sparse_bfd_set_section_contents (bfd *abfd, asection *section,
|
|
const void *data, file_ptr offset,
|
|
bfd_size_type count)
|
|
{
|
|
for (size_t i = 0; i < count; i += SPARSE_BLOCK_SIZE)
|
|
{
|
|
size_t block_size = count - i;
|
|
if (block_size > SPARSE_BLOCK_SIZE)
|
|
block_size = SPARSE_BLOCK_SIZE;
|
|
|
|
/* Ignore writing blocks of all-0s. */
|
|
if (!is_all_zeros (data + i, block_size))
|
|
{
|
|
if (!bfd_set_section_contents (abfd, section, data + i, offset + i,
|
|
block_size))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Helper function which writes a single 0 at the last byte of the last
|
|
non-empty section in the file.
|
|
|
|
Since that section is at the very end of the output core dump, this ensures
|
|
that the overall file size is valid, even if we skip writing blocks of 0s
|
|
to obtain a sparse output.
|
|
|
|
This last byte is expected to be overridden later if it should be non-0.
|
|
If something wrong happens before the end of the copy, we do expect to have
|
|
0-initialized memory in the missing sections anyway, so this byte is
|
|
valid. */
|
|
|
|
static void
|
|
ensure_last_section_size (struct out_section *descr)
|
|
{
|
|
struct out_section *last_section = NULL;
|
|
|
|
/* Output must have began so we are sure BFD has laid out all sections in
|
|
the output file. */
|
|
assert (descr->obfd->output_has_begun);
|
|
for (; descr != NULL; descr = descr->next)
|
|
{
|
|
if (descr->osection != NULL)
|
|
{
|
|
assert (descr->osection->size > 0);
|
|
if (last_section == NULL
|
|
|| descr->osection->filepos > last_section->osection->filepos)
|
|
last_section = descr;
|
|
}
|
|
}
|
|
|
|
if (last_section != NULL)
|
|
{
|
|
assert (last_section->osection != NULL
|
|
&& last_section->osection->size > 0);
|
|
const char byte = 0;
|
|
bfd_set_section_contents (last_section->obfd, last_section->osection,
|
|
&byte, last_section->osection->size - 1, 1);
|
|
}
|
|
}
|
|
|
|
/* Copy data from source file to destination file according to DESCR.
|
|
|
|
Return 0 on success, non-0 otherwise. */
|
|
|
|
static int
|
|
do_copy_load_segments (struct out_section *descr)
|
|
{
|
|
char *buf = xmalloc (COPY_SIZE);
|
|
|
|
ensure_last_section_size (descr);
|
|
|
|
for (; descr != NULL; descr = descr->next)
|
|
{
|
|
/* No data to copy for this section. */
|
|
if (descr->osection == NULL)
|
|
continue;
|
|
|
|
size_t to_copy = descr->iphdr->p_filesz;
|
|
verbose_printf (_(" Copying segment from %s offset=0x%lx,"
|
|
" size=0x%lx, vma=0x%lx\n"),
|
|
bfd_get_filename (descr->ibfd),
|
|
descr->iphdr->p_offset, descr->iphdr->p_filesz,
|
|
descr->iphdr->p_vaddr);
|
|
|
|
/* The first chunk should get us up to the next SPARSE_BLOCK_SIZE
|
|
alignment on the underlying file. */
|
|
int sparse_align = 1;
|
|
/* Copy data from the input file. */
|
|
while (to_copy > 0)
|
|
{
|
|
size_t to_read = to_copy < COPY_SIZE ? to_copy : COPY_SIZE;
|
|
if (sparse_align)
|
|
{
|
|
size_t first_chunk_size
|
|
= (SPARSE_BLOCK_SIZE
|
|
- (descr->osection->filepos % SPARSE_BLOCK_SIZE));
|
|
if (first_chunk_size != SPARSE_BLOCK_SIZE)
|
|
to_read = first_chunk_size;
|
|
|
|
sparse_align = 0;
|
|
}
|
|
|
|
if (bfd_seek (descr->ibfd, descr->iphdr->p_offset +
|
|
(descr->iphdr->p_filesz - to_copy), SEEK_SET) != 0
|
|
|| bfd_read (buf, to_read, descr->ibfd) != to_read)
|
|
{
|
|
non_fatal (_("warning: Failed to read 0x%lx bytes from %s at "
|
|
"offset 0x%lx: %s"),
|
|
to_read, bfd_get_filename (descr->ibfd),
|
|
(unsigned long) (descr->iphdr->p_offset
|
|
+ (descr->iphdr->p_filesz - to_copy)),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
break;
|
|
}
|
|
|
|
if (!sparse_bfd_set_section_contents (descr->obfd, descr->osection,
|
|
buf,
|
|
descr->iphdr->p_filesz
|
|
- to_copy,
|
|
to_read))
|
|
{
|
|
non_fatal (_("Failed to write 0x%zx bytes in %s at offset: %s"),
|
|
to_read, bfd_get_filename (descr->ibfd),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
free (buf);
|
|
return -1;
|
|
}
|
|
|
|
to_copy -= to_read;
|
|
}
|
|
}
|
|
|
|
free (buf);
|
|
return 0;
|
|
}
|
|
|
|
/* Analyze input file IBFD.
|
|
|
|
DESCR is populated with the required data which will be used later to copy
|
|
data from IBFD to OBFD.
|
|
|
|
NOTES_DATA and NOTES_DATA_SIZE gather note contents found while analysing
|
|
IBFD.
|
|
|
|
This procedure uses ELF backend data to analyse IBFD. This is so we list
|
|
segments as seen by the loader, not sections as shown by BFD. BFD would
|
|
otherwise create pseudo sections for each note it knows about, which we are
|
|
not interested in as notes are processed by gather_notes. */
|
|
|
|
static int
|
|
analyze_ibfd (bfd *obfd, bfd *ibfd,
|
|
struct out_section **descr,
|
|
int *notes_data_size, char **notes_data)
|
|
{
|
|
int ret;
|
|
struct elf_obj_tdata *tdata;;
|
|
size_t phindex;
|
|
|
|
assert (descr != NULL);
|
|
assert (notes_data_size != NULL);
|
|
assert (notes_data != NULL);
|
|
|
|
verbose_printf (_(" Analysing %s\n"), bfd_get_filename (ibfd));
|
|
tdata = ibfd->tdata.elf_obj_data;
|
|
for (phindex = 0; phindex < tdata->elf_header->e_phnum; ++phindex)
|
|
{
|
|
if (tdata->phdr[phindex].p_type == PT_LOAD)
|
|
{
|
|
if ((ret = gather_load_segments (ibfd, &tdata->phdr[phindex],
|
|
descr)) != 0)
|
|
return ret;
|
|
}
|
|
else if (tdata->phdr[phindex].p_type == PT_NOTE)
|
|
{
|
|
if ((ret = gather_notes (obfd, ibfd, &tdata->phdr[phindex],
|
|
notes_data, notes_data_size)) != 0)
|
|
return ret;
|
|
}
|
|
else
|
|
verbose_printf
|
|
(_(" Ignoring segment at %lx of type %ld\n"),
|
|
tdata->phdr[phindex].p_offset, tdata->phdr[phindex].p_type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Declare a load section of size SIZE to be loaded at address VMA in OBFD.
|
|
Attributes are set according to FLAGS. The section alignment is specified
|
|
by ALIGN. If OSECT is not NULL, it will be updated to point to the newly
|
|
created asection.
|
|
|
|
Return 0 on success, -1 on error. */
|
|
|
|
static int
|
|
declare_load_sect (bfd *obfd, flagword flags, bfd_size_type size,
|
|
bfd_vma vma, bfd_vma align, asection **osect)
|
|
{
|
|
asection *osection;
|
|
if ((osection = bfd_make_section_anyway_with_flags (obfd, "load",
|
|
flags)) == NULL
|
|
|| !bfd_set_section_size (osection, size)
|
|
|| !bfd_set_section_vma (osection, vma)
|
|
|| !bfd_set_section_lma (osection, 0))
|
|
{
|
|
non_fatal (_("Failed to create output section: %s"),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
return -1;
|
|
}
|
|
|
|
if (align >= 1)
|
|
{
|
|
/* Check that phdr->p_align is a power of 2. */
|
|
if (!is_power_of_two (align))
|
|
{
|
|
non_fatal (_("Unsupported alignment %#lx."), align);
|
|
return -1;
|
|
}
|
|
|
|
if (!bfd_set_section_alignment (osection, log2 (align)))
|
|
{
|
|
non_fatal (_("Failed to create output section."));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Init the program header for the newly created section. */
|
|
flags = PF_R;
|
|
if ((bfd_section_flags (osection) & SEC_READONLY) == 0)
|
|
flags |= PF_W;
|
|
if ((bfd_section_flags (osection) & SEC_CODE) != 0)
|
|
flags |= PF_X;
|
|
|
|
if (!bfd_record_phdr (obfd, PT_LOAD, true, flags, false, 0, false, false, 1,
|
|
&osection))
|
|
{
|
|
non_fatal (_("Failed to create program header."));
|
|
return -1;
|
|
}
|
|
|
|
if (osect != NULL)
|
|
*osect = osection;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
prepare_obfd (bfd *obfd, struct out_section *descr, int notes_data_size,
|
|
asection **note_sec)
|
|
{
|
|
verbose_printf (_("Creating output file structure.\n"));
|
|
/* Create the NOTE segment. */
|
|
*note_sec = bfd_make_section_anyway_with_flags (obfd, "note",
|
|
SEC_HAS_CONTENTS
|
|
| SEC_READONLY
|
|
| SEC_ALLOC);
|
|
if (*note_sec == NULL)
|
|
{
|
|
non_fatal (_("Failed to create the output note section: %s"),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
return -1;
|
|
}
|
|
|
|
bfd_set_section_vma (*note_sec, 0);
|
|
bfd_set_section_alignment (*note_sec, 0);
|
|
bfd_set_section_size (*note_sec, notes_data_size);
|
|
|
|
if (!bfd_record_phdr (obfd, PT_NOTE, true, PF_R, false, 0, false, false, 1,
|
|
note_sec))
|
|
{
|
|
non_fatal (_("Failed to create program headers in %s."),
|
|
bfd_get_filename (obfd));
|
|
return -1;
|
|
}
|
|
|
|
/* Create the LOAD sections. */
|
|
for (; descr != NULL; descr = descr->next)
|
|
{
|
|
descr->obfd = obfd;
|
|
|
|
flagword flags = SEC_ALLOC;
|
|
|
|
if ((descr->iphdr->p_flags & PF_W) == 0)
|
|
flags |= SEC_READONLY;
|
|
|
|
if ((descr->iphdr->p_flags & PF_X) != 0)
|
|
flags |= SEC_CODE;
|
|
else
|
|
flags |= SEC_DATA;
|
|
|
|
/* For the current segment, create a section with content
|
|
(if p_filesz > 0), a section without content
|
|
(if p_filesz < p_memsz), maybe both.
|
|
Sections without content need no further processing, we only
|
|
need to track sections for which we will need to copy data. */
|
|
if (descr->iphdr->p_filesz > 0)
|
|
{
|
|
if (declare_load_sect (obfd, flags | SEC_LOAD | SEC_HAS_CONTENTS,
|
|
descr->iphdr->p_filesz,
|
|
descr->iphdr->p_vaddr, descr->iphdr->p_align,
|
|
&descr->osection) == -1)
|
|
return -1;
|
|
}
|
|
|
|
if (descr->iphdr->p_filesz < descr->iphdr->p_memsz)
|
|
{
|
|
if (declare_load_sect (obfd, flags,
|
|
(descr->iphdr->p_memsz
|
|
- descr->iphdr->p_filesz),
|
|
(descr->iphdr->p_vaddr
|
|
+ descr->iphdr->p_filesz),
|
|
descr->iphdr->p_align, NULL) == -1)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Worker function merging contents of a host core dump HBFD and a gpu core dump
|
|
GBFD into OBFD.
|
|
|
|
This function will close OBFD, HBFD and GBFD before returning. This is
|
|
because it needs to free some data which needs to be accessed when closing
|
|
OBFD.
|
|
|
|
Return 0 on success, non-0 otherwise. */
|
|
|
|
static int
|
|
do_merge_cores (bfd *obfd, bfd *hbfd, bfd *gbfd)
|
|
{
|
|
int ret;
|
|
char *notes_buf = NULL;
|
|
int notes_buf_size = 0;
|
|
asection *note_sec = NULL;
|
|
struct out_section *descr = NULL;
|
|
|
|
/* Parse both host core and GPU core.
|
|
|
|
Create a section for each LOAD segment found and gather notes. */
|
|
if ((ret = analyze_ibfd (obfd, hbfd, &descr, ¬es_buf_size, ¬es_buf))
|
|
!= 0
|
|
|| (ret = analyze_ibfd (obfd, gbfd, &descr, ¬es_buf_size, ¬es_buf))
|
|
!= 0)
|
|
goto out;
|
|
|
|
if (prepare_obfd (obfd, descr, notes_buf_size, ¬e_sec))
|
|
goto out;
|
|
|
|
/* Start the output by writing the note section out. */
|
|
if (!bfd_set_section_contents (obfd, note_sec, notes_buf, 0,
|
|
notes_buf_size))
|
|
{
|
|
non_fatal (_("Failed to write notes in %s: %s"), bfd_get_filename (obfd),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if ((ret = do_copy_load_segments (descr)) != 0)
|
|
goto out;
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
while (descr != NULL)
|
|
{
|
|
struct out_section *tmp = descr;
|
|
descr = descr->next;
|
|
free (tmp);
|
|
}
|
|
free (notes_buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Open PATH with bfd and ensure that it is an Elf64 based core file.
|
|
|
|
Return NULL if the file cannot be opened, or if the format is not as
|
|
expected. */
|
|
|
|
static bfd *
|
|
open_core (const char *path)
|
|
{
|
|
bfd *ret;
|
|
ret = bfd_openr (path, NULL);
|
|
if (ret == NULL)
|
|
{
|
|
non_fatal (_("Failed to open %s: %s"), path,
|
|
bfd_errmsg (bfd_get_error ()));
|
|
return NULL;
|
|
}
|
|
|
|
if (!bfd_check_format (ret, bfd_core))
|
|
{
|
|
non_fatal (_("%s is not a core file: %s"), bfd_get_filename (ret),
|
|
bfd_errmsg (bfd_get_error ()));
|
|
bfd_close (ret);
|
|
return NULL;
|
|
}
|
|
|
|
/* This tool only handles ELF64 files. */
|
|
if (bfd_get_flavour (ret) != bfd_target_elf_flavour
|
|
|| get_elf_backend_data (ret)->s->elfclass != ELFCLASS64)
|
|
{
|
|
non_fatal (_("%s is not an Elf64 based core file."),
|
|
bfd_get_filename (ret));
|
|
bfd_close (ret);
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Merge GPU_CORE and HOST_CORE into OUT. */
|
|
|
|
static int
|
|
merge_core (const char *out, const char *host_core, const char *gpu_core)
|
|
{
|
|
bfd *obfd;
|
|
bfd *hbfd;
|
|
bfd *gbfd;
|
|
int ret;
|
|
|
|
if (access (out, F_OK) == 0 && !force)
|
|
{
|
|
non_fatal (_("File %s already exist. Use '-f' to override."), out);
|
|
return -1;
|
|
}
|
|
|
|
/* First open the host core file as we will copy fields from it. */
|
|
hbfd = open_core (host_core);
|
|
if (hbfd == NULL)
|
|
return -1;
|
|
|
|
gbfd = open_core (gpu_core);
|
|
if (gbfd == NULL)
|
|
{
|
|
bfd_close (hbfd);
|
|
return -1;
|
|
}
|
|
|
|
verbose_printf (_("Creating %s\n"), out);
|
|
if ((obfd = bfd_openw (out, bfd_get_target (hbfd))) == NULL
|
|
|| !bfd_set_format (obfd, bfd_core)
|
|
|| !bfd_set_arch_mach (obfd, bfd_get_arch (hbfd), bfd_get_mach (hbfd)) )
|
|
{
|
|
non_fatal (_("Failed to create %s: %s"), out,
|
|
bfd_errmsg (bfd_get_error ()));
|
|
bfd_close (hbfd);
|
|
bfd_close (gbfd);
|
|
return -1;
|
|
}
|
|
|
|
ret = do_merge_cores (obfd, hbfd, gbfd);
|
|
|
|
bfd_close (obfd);
|
|
bfd_close (hbfd);
|
|
bfd_close (gbfd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int c;
|
|
int exit_status;
|
|
|
|
#ifdef HAVE_LC_MESSAGES
|
|
setlocale (LC_MESSAGES, "");
|
|
#endif
|
|
setlocale (LC_CTYPE, "");
|
|
|
|
bindtextdomain (PACKAGE, LOCALEDIR);
|
|
textdomain (PACKAGE);
|
|
|
|
program_name = *argv;
|
|
xmalloc_set_program_name (program_name);
|
|
bfd_set_error_program_name (program_name);
|
|
|
|
expandargv (&argc, &argv);
|
|
|
|
if (bfd_init () != BFD_INIT_MAGIC)
|
|
fatal (_("fatal error: libbfd ABI mismatch"));
|
|
set_default_bfd_target ();
|
|
|
|
while ((c = getopt_long (argc, argv, "vhVf", long_options, (int *) NULL))
|
|
!= -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 0:
|
|
break; /* We've been given a long option. */
|
|
case 'v':
|
|
show_version = true;
|
|
break;
|
|
|
|
case 'V':
|
|
verbose = true;
|
|
break;
|
|
|
|
case 'h':
|
|
usage (stdout, 0);
|
|
break;
|
|
|
|
case 'f':
|
|
force = true;
|
|
break;
|
|
|
|
default:
|
|
usage (stdout, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (show_version)
|
|
print_version (program_name);
|
|
|
|
/* We must have 3 positional arguments provided:
|
|
- the output file
|
|
- 2 files to merge. */
|
|
if (optind + 3 > argc)
|
|
{
|
|
non_fatal (_("Missing positional arguments.\n"));
|
|
usage (stderr, 2);
|
|
}
|
|
|
|
exit_status
|
|
= merge_core (argv[optind], argv[optind + 1], argv[optind + 2]);
|
|
|
|
return exit_status;
|
|
}
|