glibc/sysdeps/e2k/dl-machine.h

584 lines
23 KiB
C

/* Copyright (c) 2016 ZAO "MCST". All rights reserved. */
/* Machine-dependent ELF dynamic relocation inline functions. e2k version.
Copyright (C) 1995-2005, 2006 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C 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; either
version 2.1 of the License, or (at your option) any later version.
The GNU C 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 the GNU C Library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */
/* This one is required to make it possible to use N_("") macro when emitting
error notifications via `_dl_signal_error ()' in nested `dl-machine.h'
files. */
#include <libintl.h>
static inline void flush_icache_range (unsigned long start, unsigned long end);
#include_next <dl-machine.h>
#ifndef dl_machine_h
#define dl_machine_h
#define ELF_MACHINE_NAME "e2k"
/* Return nonzero iff ELF header is compatible with the running host. */
static inline int __attribute__ ((unused))
elf_machine_matches_host (const ElfW(Ehdr) *ehdr)
{
return (ehdr->e_machine == EM_MCST_ELBRUS
|| ehdr->e_machine == EM_E2K_OLD);
}
/* Return the link-time address of _DYNAMIC. Conveniently, this is the
first element of the GOT, a special entry that is never relocated. */
static inline ElfW(Addr) __attribute__ ((unused, const))
elf_machine_dynamic (void)
{
/* This produces a GOTOFF reloc that resolves to zero at link time, so in
fact just loads from the GOT register directly. By doing it without
an asm we can let the compiler choose any register. */
extern const ElfW(Addr) _GLOBAL_OFFSET_TABLE_[] attribute_hidden;
return _GLOBAL_OFFSET_TABLE_[0];
}
/* Return the run-time load address of the shared object. */
static inline ElfW(Addr) __attribute__ ((unused))
elf_machine_load_address (void)
{
/* Compute the difference between the runtime address of _DYNAMIC as seen
by a GOTOFF reference, and the link-time address found in the special
unrelocated first GOT entry. */
extern ElfW(Dyn) bygotoff[] asm ("_DYNAMIC") attribute_hidden;
return (ElfW(Addr)) &bygotoff - elf_machine_dynamic ();
}
/* E2k overlaps DT_RELA and DT_PLTREL. */
#define ELF_MACHINE_PLTREL_OVERLAP 1
#define MAS_ICACHE_LINE_FLUSH 0x17
#define E2K_ICACHE_SET_SIZE 256
#define E2K_ICACHE_SET_MASK (E2K_ICACHE_SET_SIZE - 1)
static inline void
flush_icache_range (unsigned long start, unsigned long end)
{
unsigned long addr;
for (addr = start & ~E2K_ICACHE_SET_MASK;
addr < ((end + E2K_ICACHE_SET_MASK) & ~E2K_ICACHE_SET_MASK);
addr += E2K_ICACHE_SET_SIZE)
{
/* E2K_WAIT_ALL; */
/* Exotic operations like WAIT are no longer supported in asm_inline
`asm's (see Bug #69048). */
#pragma no_asm_inline
asm volatile ("wait \tma_c = 1, fl_c = 1, ld_c = 1, st_c = 1, all_e = 1, all_c = 1");
/* E2K_WRITE_MAS_D((long) 0x0, addr, MAS_ICACHE_LINE_FLUSH); */
#pragma no_asm_inline
asm volatile ("std, 2\t0x0, %0, %1, mas = %2" : : "r" ((unsigned long long) 0x0), "r" ((unsigned long long) addr), "i" (MAS_ICACHE_LINE_FLUSH));
/* E2K_WAIT_ALL; */
#pragma no_asm_inline
/* Exotic operations like WAIT are no longer supported in asm_inline
`asm's (see Bug #69048). */
asm volatile ("wait \tma_c = 1, fl_c = 1, ld_c = 1, st_c = 1, all_e = 1, all_c = 1");
}
}
extern ElfW(Addr) _dl_fixup (struct link_map *l, ElfW(Word) reloc_offset);
static inline int __attribute__ ((unused, always_inline))
elf_machine_runtime_setup (struct link_map *l, int lazy, int profile)
{
/* Lazy binding is supported only for modules with DT_E2K_LAZY. */
if (l->l_info[DT_E2K_LAZY - DT_LOPROC + DT_NUM] != NULL
&& l->l_info[DT_PLTGOT] != NULL)
{
ElfW(Addr) *plt = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
E2K_RUNTIME_SETUP;
return lazy;
}
else if (l->l_info[DT_E2K_LAZY_GOT - DT_LOPROC + DT_NUM] != NULL
&& l->l_info[DT_PLTGOT] != NULL)
{
ElfW(Addr) *got = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
ElfW(Addr) fixup_addr = (ElfW(Addr)) _dl_fixup;
ElfW(Addr) l_addr = (ElfW(Addr)) l;
got[1] = l_addr;
got[2] = fixup_addr;
return lazy;
}
else if (l->l_info[DT_E2K_LAZY_DEFECTIVE - DT_LOPROC + DT_NUM] != NULL
&& l->l_info[DT_PLTGOT] != NULL)
{
ElfW(Addr) *plt = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
E2K_RUNTIME_DEFECTIVE_SETUP;
/* Do we need to flush icache here at all? Has anyone executed
any code within PLT range already? In case we do, the whole
PLT range should be flushed rather than just its header,
now that Bug #60151 is fixed in runtime. */
#if 0
flush_icache_range ((unsigned long) &plt[E2K_RTIME_MIN],
(unsigned long) &plt[E2K_RTIME_MAX]);
#endif /* 0 */
return lazy;
}
else if (l->l_info[DT_E2K_LAZY_BUG_75842 - DT_LOPROC + DT_NUM] != NULL
&& l->l_info[DT_PLTGOT] != NULL)
{
ElfW(Addr) *plt = (ElfW(Addr) *) D_PTR (l, l_info[DT_PLTGOT]);
E2K_RUNTIME_BUG_75842_SETUP;
return lazy;
}
return 0;
}
/* This function used to be nested in the original sources of glibc, the reason
for which was a mystery to me, because it didn't access local variables
belonging to its callers. */
static inline void
elf_machine_lazy_rel (struct link_map *map, ElfW(Addr) l_addr,
const ElfW(Rela) *reloc, int skip_ifunc,
struct r_scope_elem *scope[], const char *strtab)
{
/* For defective PLT entries everything is done via E2K_RUNTIME_DEFECTIVE
_SETUP. They require additional conversion and I've no desire to change
anything related to their support before it's thrown away. */
if (map->l_info[DT_E2K_LAZY_DEFECTIVE - DT_LOPROC + DT_NUM] != NULL)
return;
/* FIND OUT what's the point in having a separate `l_addr' parameter?
Why can't we get it from MAP? */
ElfW(Addr) *const reloc_addr = (void *) (l_addr + reloc->r_offset);
if (map->l_info[DT_E2K_LAZY - DT_LOPROC + DT_NUM] != NULL)
{
if (ELFXX_R_TYPE (reloc->r_info) == R_E2K_XX_JMP_SLOT)
{
#if XX == 32
if (reloc_addr[3] == 0x33333333)
/* ATTENTION: here `map->l_addr' is employed unlike the code
fragment a few lines above. . . */
reloc_addr[3] = (Elf32_Addr) D_PTR (map, l_info[DT_PLTGOT]);
#else /* XX == 64 */
if (reloc_addr[1] == 0x3333333333333333ULL)
{
/* ATTENTION: here `map->l_addr' is employed unlike the code
fragment a few lines above. . . */
Elf64_Addr plt_start
= (Elf64_Addr) D_PTR (map, l_info[DT_PLTGOT]);
Elf32_Addr *p_plt_start = (Elf32_Addr *) &plt_start;
Elf64_Addr plt_start_lit = ((((Elf64_Addr) p_plt_start[0]) << 32)
+ (Elf64_Addr) p_plt_start[1]);
reloc_addr[1] = plt_start_lit;
}
#endif /* XX == 64 */
else
/* FIND OUT how this can happen. Is repeated initial "adjustment"
of a PLT entry harmful at all? */
_dl_signal_error (0, map->l_name, NULL,
N_("elf_machine_lazy_rel ivoked multiple times "
"for the same PLT entry"));
}
else
_dl_reloc_bad_type (map, ELFW(R_TYPE) (reloc->r_info), 1);
}
else if (map->l_info[DT_E2K_LAZY_GOT - DT_LOPROC + DT_NUM] != NULL)
{
if (ELFXX_R_TYPE (reloc->r_info) == R_E2K_XX_JMP_SLOT)
*reloc_addr += l_addr;
else if (ELFXX_R_TYPE (reloc->r_info) == R_E2K_XX_IRELATIVE)
{
/* Note that "lazy processing of R_E2K_XX_IRELATIVE" seems to be a
misnomer. In fact the job related to this relocation is never
delegated to `_dl_fixup ()', which is capable of processing one
relocation type only being assigned to ELF_MACHINE_JMP_SLOT. For
E2K this is `R_E2K_XX_JMP_SLOT'. At the same time processing
R_E2K_XX_IRELATIVE doesn't require a lookup through the list of
loaded libraries and thus can be easily accomplished at once.
With the aforesaid in mind, the final relocation value should be
evaluated here. What's the point in placing it to `.rela.plt'
then?
`SKIP_IFUNC == 1' case corresponds to trace mode, which lets one
eliminate the need for calling the resolver. */
ElfW(Addr) value = map->l_addr + reloc->r_addend;
/* What are we going to obtain if SKIP_IFUNC turns out to be TRUE I
wonder . . . ? */
if (__glibc_likely (!skip_ifunc))
value = ((ElfW(Addr) (*) (void)) value) ();
*reloc_addr = value;
}
else
_dl_reloc_bad_type (map, ELFW(R_TYPE) (reloc->r_info), 1);
}
}
/* Fixup a PLT entry to bounce directly to the function at VALUE. */
static inline ElfW(Addr)
elf_machine_fixup_plt (struct link_map *map,
lookup_t t,
const ElfW(Sym) *refsym, const ElfW(Sym) *sym,
const ElfW(Rela) *reloc,
ElfW(Addr) *reloc_addr,
ElfW(Addr) value)
{
/* There is probably no point in verifying RELOC once more, now that this has
already been done in `elf_machine_lazy_rel', is there? */
if (map->l_info[DT_E2K_LAZY - DT_LOPROC + DT_NUM] != NULL
|| map->l_info[DT_E2K_LAZY_DEFECTIVE - DT_LOPROC + DT_NUM] != NULL)
{
#if XX == 32
reloc_addr[3] = value;
#else /* XX == 64 */
Elf64_Addr *reloc_addr64 = (Elf64_Addr *) reloc_addr;
Elf32_Addr *p_val64 = (Elf32_Addr *) &value;
Elf64_Addr lit_val = ((((Elf64_Addr) p_val64[0]) << 32)
+ (Elf64_Addr) p_val64[1]);
reloc_addr64[1] = lit_val;
#endif /* XX == 64 */
flush_icache_range ((unsigned long) &reloc_addr[0],
(unsigned long) &reloc_addr[6]);
}
else if (map->l_info[DT_E2K_LAZY_BUG_75842 - DT_LOPROC + DT_NUM] != NULL)
E2K_FIXUP_LAZY_BUG_75842_PLT (reloc_addr, value);
else if (map->l_info[DT_E2K_LAZY_GOT - DT_LOPROC + DT_NUM] != NULL)
*reloc_addr = value;
return value;
}
#define ELF_MACHINE_JMP_SLOT R_E2K_XX_JMP_SLOT
#define ARCH_LA_PLTENTER e2kXX_gnu_pltenter
#define ARCH_LA_PLTEXIT e2kXX_gnu_pltexit
#define ELF_MACHINE_NO_REL 1
#define ELF_MACHINE_NO_RELA 0
#define elf_machine_plt_value(map,reloc,value) value
#define FIX_USER_STACK \
extern unsigned int _dl_skip_args; \
static void \
_fix_user_stack (long *user_stack) \
{ \
int *pargc; \
int i, null_cntr = 0; \
\
pargc = (int *) user_stack; \
*pargc -= _dl_skip_args; \
\
/* Shift by _dl_skip_args elements downwards three arrays \
adjacent in memory: argv, envp and auxv. When doing so \
make use of the fact that the trailing element in each \
array is NULL. */ \
for (i = 1; ; i++) \
{ \
user_stack[i] = user_stack[i + _dl_skip_args]; \
if (user_stack[i] == 0L) \
{ \
if (++null_cntr == 3) \
break; \
} \
} \
\
/* In case ld.so is run explicitly _dl_argv is increased in \
parallel with the evaluation of _dl_skip_args in _dl_main. \
On those platforms where stack pointer can't be adjusted \
before passing control to the main executable and argv[], \
envp[] and auxv[] need to be shifted (e.g. sparc, ia64, \
e2k) it's crucial to revert _dl_argv. */ \
_dl_argv -= _dl_skip_args; \
}
#if defined __LCC__
# define register_long_fp register long *fp asm ("FP")
#define CPU_INIT \
__builtin_cpu_init_ext ()
#else /* defined __LCC__ */
# define register_long_fp register long *fp asm ("%r8")
# define CPU_INIT
#endif /* defined __LCC__ */
#define RTLD_START \
\
FIX_USER_STACK \
static ElfW(Addr) _dl_start (void *arg); \
\
ElfW(Addr) \
_start2 (void) \
{ \
register_long_fp; \
int argc; \
char **argv, **envp; \
ElfW(Addr) entry_point; \
\
extern void _dl_init (struct link_map *main_map, int argc, char **argv, char **env); \
\
CPU_INIT; \
\
entry_point = _dl_start (fp); \
_fix_user_stack (fp); \
\
argc = *fp; \
argv = (char**)(fp + 1); \
envp = argv + argc + 1; \
\
/* For most other architectures _dl_init( *_rtld_local,...) call is \
performed in assembler here. Do the same for E2K in C. */ \
_dl_init (_rtld_local._dl_ns[0]._ns_loaded, argc, argv, envp); \
\
return entry_point; \
} \
\
RTLD_START_EARLY
/* ELF_RTYPE_CLASS_PLT iff TYPE describes relocation of a PLT entry or
TLS variable, so undefined references should not be allowed to
define the value.
ELF_RTYPE_CLASS_NOCOPY iff TYPE should not be allowed to resolve to one
of the main executable's symbols, as for a COPY reloc. */
# define elf_machine_type_class(type) \
((((type) == R_E2K_XX_JMP_SLOT || (type) == R_E2K_TLS_XX_DTPMOD \
|| (type) == R_E2K_TLS_XX_DTPREL \
|| (type) == R_E2K_TLS_XX_TPREL) * ELF_RTYPE_CLASS_PLT) \
| (((type) == R_E2K_XX_COPY) * ELF_RTYPE_CLASS_COPY) \
| (((type) == R_E2K_XX_ABS) * ELF_RTYPE_CLASS_EXTERN_PROTECTED_DATA))
#endif /* !dl_machine_h */
#ifdef RESOLVE_MAP
#if !defined RTLD_BOOTSTRAP || defined _NDEBUG
#define DL_RELOC_BAD_TYPE(__reloc, __reloc_addr, __value) \
default: \
_dl_debug_printf("Incorrect reloc type\n"); \
_dl_debug_printf("%x\n", (long long) __reloc->r_info); \
_dl_debug_printf("%x\n", (long long) __reloc_addr); \
_dl_debug_printf("%x\n", (long long) __value); \
_dl_debug_printf("----\n"); \
break
#else /* !defined RTLD_BOOTSTRAP || defined _NDEBUG */
#define DL_RELOC_BAD_TYPE(__reloc, __reloc_addr, __value)
#endif /* !define RTLD_BOOTSTRAP || defined _NDEBUG */
/* Perform the relocation specified by RELOC and SYM (which is fully resolved).
MAP is the object containing the reloc. */
static void
elf_machine_rela (struct link_map *map, const ElfW(Rela) *reloc,
const ElfW(Sym) *sym, const struct r_found_version *version,
void *const reloc_addr_arg, int skip_ifunc,
struct r_scope_elem *scope[], const char *strtab)
{
ElfW (Addr) *const reloc_addr = reloc_addr_arg;
const unsigned long r_type = ELFXX_R_TYPE (reloc->r_info);
#if !defined RTLD_BOOTSTRAP && !defined HAVE_Z_COMBRELOC
/* This is defined in rtld.c, but nowhere in the static libc.a; make the
reference weak so static programs can still link. This declaration
cannot be done when compiling rtld.c (i.e. #ifdef RTLD_BOOTSTRAP)
because rtld.c contains the common defn for _dl_rtld_map, which is
incompatible with a weak decl in the same file. */
weak_extern (_dl_rtld_map);
#endif
if (r_type == R_E2K_XX_RELATIVE)
{
#ifndef RTLD_BOOTSTRAP
/* Already done in rtld itself. */
if (map != &GL (dl_rtld_map))
#endif /* RTLD_BOOTSTRAP */
*reloc_addr = map->l_addr + reloc->r_addend;
}
/* This one should be never met in ld.so itself but in poorly
linked libraries only. */
else if (ELFXX_R_TYPE (reloc->r_info) == R_E2K_64_RELATIVE_LIT)
{
/* Should not be met in rtld itself. */
#ifndef RTLD_BOOTSTRAP
if (map != &GL(dl_rtld_map))
#endif /* RTLD_BOOTSTRAP */
{
Elf64_Addr value = map->l_addr + reloc->r_addend;
*((Elf32_Addr *) reloc_addr) = (Elf32_Addr) (value >> 32);
*((Elf32_Addr *) reloc_addr + 1) = (Elf32_Addr) (value & 0xffffffff);
}
}
else if (ELFXX_R_TYPE (reloc->r_info) != R_E2K_NONE)
{
#ifndef RTLD_BOOTSTRAP
const ElfW(Sym) *const refsym = sym;
#endif /* RTLD_BOOTSTRAP */
ElfW(Addr) value;
struct link_map *sym_map;
if (sym->st_shndx != SHN_UNDEF
&& ELFXX_ST_BIND (sym->st_info) == STB_LOCAL)
{
sym_map = map;
value = map->l_addr;
}
else
{
sym_map = RESOLVE_MAP (&sym, version,
ELFXX_R_TYPE (reloc->r_info));
value = sym_map == NULL ? 0 : sym_map->l_addr;
if (sym)
{
value += sym->st_value;
if (__builtin_expect (sym->st_shndx != SHN_UNDEF, 1)
&& __builtin_expect ((ELFW(ST_TYPE) (sym->st_info)
== STT_GNU_IFUNC),
0)
&& __builtin_expect (!skip_ifunc, 1))
value = ((ElfW(Addr) (*) (void)) value) ();
}
}
/* Assume copy relocs have zero addend. */
value += reloc->r_addend;
switch (ELFXX_R_TYPE (reloc->r_info))
{
case R_E2K_32_SIZE:
*((Elf32_Addr *) reloc_addr) = sym->st_size + reloc->r_addend;
break;
case R_E2K_64_SIZE:
*((Elf64_Addr *) reloc_addr) = sym->st_size + reloc->r_addend;
break;
case R_E2K_32_ABS:
/* FIXME: allow for "empty" entries filled in with zeroes in the
relocation table for backward compatibility. They may have
appeared in the past in case LD allocated more space for dynamic
relocations than needed. Nowadays excessive relocations are tagged
with R_E2K_NONE. Note that unlike most other architectures on E2K
R_E2K_NONE != 0 because 0 has improvidently been reserved for
R_E2K_32_ABS from the very beginning. */
if (reloc->r_offset)
*reloc_addr = value;
break;
#ifdef dl_machine_64_h
/* In case a 64-bit library is being built the macro mentioned
above has been defined in the internal <dl-machine.h>. */
case R_E2K_64_ABS:
*reloc_addr = value;
break;
case R_E2K_64_ABS_LIT:
*((Elf32_Addr *) reloc_addr) = (Elf32_Addr) (value >> 32);
*((Elf32_Addr *) reloc_addr + 1)
= (Elf32_Addr) (value & 0xffffffff);
break;
#endif /* dl_machine_64_h */
case R_E2K_XX_JMP_SLOT:
if (map->l_info[DT_E2K_LAZY_GOT - DT_LOPROC + DT_NUM] != NULL
|| map->l_info[DT_E2K_LAZY_BUG_75842 - DT_LOPROC + DT_NUM] != NULL
|| map->l_info[DT_E2K_LAZY - DT_LOPROC + DT_NUM] != NULL
|| (map->l_info[DT_E2K_LAZY_DEFECTIVE - DT_LOPROC + DT_NUM]
!= NULL))
elf_machine_fixup_plt (map, NULL, NULL, NULL, reloc, reloc_addr,
value);
else
E2K_FIXUP_PLT (reloc_addr, value);
break;
#ifndef RTLD_BOOTSTRAP
case R_E2K_XX_COPY:
if (sym == NULL)
/* This can happen in trace mode if an object could
not be found. */
break;
if (sym->st_size > refsym->st_size
|| (GLRO (dl_verbose) && sym->st_size < refsym->st_size))
{
extern char **_dl_argv;
const char *strtab;
strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
_dl_error_printf
("%s: Symbol `%s' has different size in shared object, "
"consider re-linking\n",
_dl_argv[0] ?: "<program name unknown>",
strtab + refsym->st_name);
}
memcpy (reloc_addr, (void *) value,
MIN (sym->st_size, refsym->st_size));
break;
case R_E2K_TLS_XX_DTPMOD:
*reloc_addr = sym_map->l_tls_modid;
break;
case R_E2K_TLS_XX_DTPREL:
*reloc_addr = value - sym_map->l_addr;
break;
case R_E2K_TLS_XX_TPREL:
CHECK_STATIC_TLS (map, sym_map);
*reloc_addr = value + sym_map->l_tls_offset - sym_map->l_addr;
break;
case R_E2K_XX_IRELATIVE:
/* This presumably duplicates the related activity in `elf_machine_
lazy_rel ()' in LD_BIND_NOW case. FIXME: SKIP_IFUNC case isn't
considered here by analogy with i386, which is probably a bug.
Note that it's taken into account above for non-local ifuncs. By
the way, can't we find ourselves here for a non-local ifunc and
thus end up calling its resolver more than once? */
value = map->l_addr + reloc->r_addend;
value = ((ElfW(Addr) (*) (void)) value) ();
*reloc_addr = value;
break;
#else /* RTLD_BOOTSTRAP */
case R_E2K_TLS_XX_DTPMOD:
*reloc_addr = 1;
break;
#endif /* RTLD_BOOTSTRAP */
default:
_dl_reloc_bad_type (map, ELFW(R_TYPE) (reloc->r_info), 0);
}
}
}
static void __attribute__ ((unused))
elf_machine_rela_relative (ElfW(Addr) l_addr, const ElfW(Rela) *reloc,
void *const reloc_addr_arg)
{
/* It should be impossible to find myself here until I implement grouping
of relative relocs within an ELF file along with DT_RELACOUNT. */
((ElfW(Addr) *)0)[0] = l_addr + reloc->r_addend;
}
#endif /* RESOLVE_MAP */