Fix objcopy for little-endian MIPS64 objects.

MIPS64 does not store the 'r_info' field of a relocation table entry as
a 64-bit value consisting of a 32-bit symbol index in the high 32 bits
and a 32-bit type in the low 32 bits as on other architectures.  Instead,
the 64-bit 'r_info' field is really a 32-bit symbol index followed by four
individual byte type fields.  For big-endian MIPS64, treating this as a
64-bit integer happens to be compatible with the layout expected by other
architectures (symbol index in upper 32-bits of resulting "native" 64-bit
integer).  However, for little-endian MIPS64 the parsed 64-bit integer
contains the symbol index in the low 32 bits and the 4 individual byte
type fields in the upper 32-bits (but as if the upper 32-bits were
byte-swapped).

To cope, add two helper routines in gelf_getrel.c to translate between the
correct native 'r_info' value and the value obtained after the normal
byte-swap translation.  Use these routines in gelf_getrel(), gelf_getrela(),
gelf_update_rel(), and gelf_update_rela().  This fixes 'readelf -r' on
little-endian MIPS64 objects which was previously decoding incorrect
relocations as well as 'objcopy: invalid symbox index' warnings from
objcopy when extracting debug symbols from kernel modules.

Even with this fixed, objcopy was still crashing when trying to extract
debug symbols from little-endian MIPS64 modules.  The workaround in
gelf_*rel*() depends on the current ELF object having a valid ELF header
so that the 'e_machine' field can be compared against EM_MIPS.  objcopy
was parsing the relocation entries to possibly rewrite the 'r_info' fields
in the update_relocs() function before writing the initial ELF header to
the destination object file.  Move the initial write of the ELF header
earlier before copy_contents() so that update_relocs() uses the correct
symbol index values.

Note that this change should really go upstream.  The binutils readelf
source has a similar hack for MIPS64EL though I implemented this version
from scratch using the MIPS64 ABI PDF as a reference.

Discussed with:	jkoshy
Reviewed by:	emaste, imp
Approved by:	re (gjb, kib)
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D15734
This commit is contained in:
John Baldwin 2018-09-05 20:51:53 +00:00
parent 0125fb6352
commit eb81f38a62
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=338478
7 changed files with 107 additions and 9 deletions

View File

@ -371,6 +371,14 @@ create_elf(struct elfcopy *ecp)
if ((ecp->flags & SYMTAB_EXIST) != 0)
create_symtab(ecp);
/*
* Write the underlying ehdr. Note that it should be called
* before elf_setshstrndx() since it will overwrite e->e_shstrndx.
*/
if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
elf_errmsg(-1));
/*
* First processing of output sections: at this stage we copy the
* content of each section from input to output object. Section
@ -380,14 +388,6 @@ create_elf(struct elfcopy *ecp)
*/
copy_content(ecp);
/*
* Write the underlying ehdr. Note that it should be called
* before elf_setshstrndx() since it will overwrite e->e_shstrndx.
*/
if (gelf_update_ehdr(ecp->eout, &oeh) == 0)
errx(EXIT_FAILURE, "gelf_update_ehdr() failed: %s",
elf_errmsg(-1));
/* Generate section name string table (.shstrtab). */
set_shstrtab(ecp);

View File

@ -35,6 +35,7 @@ SRCS= elf.c \
gelf_ehdr.c \
gelf_getclass.c \
gelf_fsize.c \
gelf_mips64el.c \
gelf_move.c \
gelf_phdr.c \
gelf_rel.c \

View File

@ -216,12 +216,15 @@ int (*_libelf_get_translator(Elf_Type _t, int _direction, int _elfclass))
void *_libelf_getphdr(Elf *_e, int _elfclass);
void *_libelf_getshdr(Elf_Scn *_scn, int _elfclass);
void _libelf_init_elf(Elf *_e, Elf_Kind _kind);
int _libelf_is_mips64el(Elf *e);
int _libelf_load_section_headers(Elf *e, void *ehdr);
unsigned int _libelf_malign(Elf_Type _t, int _elfclass);
Elf *_libelf_memory(unsigned char *_image, size_t _sz, int _reporterror);
size_t _libelf_msize(Elf_Type _t, int _elfclass, unsigned int _version);
void *_libelf_newphdr(Elf *_e, int _elfclass, size_t _count);
Elf *_libelf_open_object(int _fd, Elf_Cmd _c, int _reporterror);
Elf64_Xword _libelf_mips64el_r_info_tof(Elf64_Xword r_info);
Elf64_Xword _libelf_mips64el_r_info_tom(Elf64_Xword r_info);
struct _Libelf_Data *_libelf_release_data(struct _Libelf_Data *_d);
Elf *_libelf_release_elf(Elf *_e);
Elf_Scn *_libelf_release_scn(Elf_Scn *_s);

View File

@ -0,0 +1,81 @@
/*-
* Copyright (c) 2018 John Baldwin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <gelf.h>
#include "_libelf.h"
ELFTC_VCSID("$Id$");
int
_libelf_is_mips64el(Elf *e)
{
return (e->e_kind == ELF_K_ELF && e->e_byteorder == ELFDATA2LSB &&
e->e_u.e_elf.e_ehdr.e_ehdr64->e_machine == EM_MIPS);
}
/*
* For MIPS64, the r_info field is actually stored as a 32-bit symbol
* index (r_sym) followed by four single-byte fields (r_ssym, r_type3,
* r_type2, and r_type). The byte-swap for the little-endian case
* jumbles this incorrectly so compensate.
*/
Elf64_Xword
_libelf_mips64el_r_info_tof(Elf64_Xword r_info)
{
Elf64_Xword new_info;
uint8_t ssym, type3, type2, type;
ssym = r_info >> 24;
type3 = r_info >> 16;
type2 = r_info >> 8;
type = r_info;
new_info = r_info >> 32;
new_info |= (Elf64_Xword)ssym << 32;
new_info |= (Elf64_Xword)type3 << 40;
new_info |= (Elf64_Xword)type2 << 48;
new_info |= (Elf64_Xword)type << 56;
return (new_info);
}
Elf64_Xword
_libelf_mips64el_r_info_tom(Elf64_Xword r_info)
{
Elf64_Xword new_info;
uint8_t ssym, type3, type2, type;
ssym = r_info >> 32;
type3 = r_info >> 40;
type2 = r_info >> 48;
type = r_info >> 56;
new_info = (r_info & 0xffffffff) << 32;
new_info |= (Elf64_Xword)ssym << 24;
new_info |= (Elf64_Xword)type3 << 16;
new_info |= (Elf64_Xword)type2 << 8;
new_info |= (Elf64_Xword)type;
return (new_info);
}

View File

@ -90,6 +90,9 @@ gelf_getrel(Elf_Data *ed, int ndx, GElf_Rel *dst)
rel64 = (Elf64_Rel *) d->d_data.d_buf + ndx;
*dst = *rel64;
if (_libelf_is_mips64el(e))
dst->r_info = _libelf_mips64el_r_info_tom(rel64->r_info);
}
return (dst);
@ -156,6 +159,9 @@ gelf_update_rel(Elf_Data *ed, int ndx, GElf_Rel *dr)
rel64 = (Elf64_Rel *) d->d_data.d_buf + ndx;
*rel64 = *dr;
if (_libelf_is_mips64el(e))
rel64->r_info = _libelf_mips64el_r_info_tof(dr->r_info);
}
return (1);

View File

@ -91,6 +91,10 @@ gelf_getrela(Elf_Data *ed, int ndx, GElf_Rela *dst)
rela64 = (Elf64_Rela *) d->d_data.d_buf + ndx;
*dst = *rela64;
if (_libelf_is_mips64el(e))
dst->r_info =
_libelf_mips64el_r_info_tom(rela64->r_info);
}
return (dst);
@ -159,6 +163,9 @@ gelf_update_rela(Elf_Data *ed, int ndx, GElf_Rela *dr)
rela64 = (Elf64_Rela *) d->d_data.d_buf + ndx;
*rela64 = *dr;
if (_libelf_is_mips64el(e))
rela64->r_info = _libelf_mips64el_r_info_tof(dr->r_info);
}
return (1);

View File

@ -60,7 +60,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
#define __FreeBSD_version 1200083 /* Master, propagated to newvers */
#define __FreeBSD_version 1200084 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,