0,0 → 1,214 |
/* pseudo-reloc.c |
|
Contributed by Egor Duda <deo@logos-m.ru> |
Modified by addition of runtime_pseudo_reloc version 2 |
by Kai Tietz <kai.tietz@onevision.com> |
|
THIS SOFTWARE IS NOT COPYRIGHTED |
|
This source code is offered for use in the public domain. You may |
use, modify or distribute it freely. |
|
This code is distributed in the hope that it will be useful but |
WITHOUT ANY WARRANTY. ALL WARRENTIES, EXPRESS OR IMPLIED ARE HEREBY |
DISCLAMED. This includes but is not limited to warrenties of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
*/ |
|
#include <stdint.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdarg.h> |
#include <memory.h> |
|
extern char __RUNTIME_PSEUDO_RELOC_LIST__; |
extern char __RUNTIME_PSEUDO_RELOC_LIST_END__; |
extern char _image_base__; |
|
void _pei386_runtime_relocator (void); |
|
/* v1 relocation is basically: |
* *(base + .target) += .addend |
* where (base + .target) is always assumed to point |
* to a DWORD (4 bytes). |
*/ |
typedef struct { |
uint32_t addend; |
uint32_t target; |
} runtime_pseudo_reloc_item_v1; |
|
/* v2 relocation is more complex. In effect, it is |
* *(base + .target) += *(base + .sym) - (base + .sym) |
* with care taken in both reading, sign extension, and writing |
* because .flags may indicate that (base + .target) may point |
* to a BYTE, WORD, DWORD, or QWORD (w64). |
*/ |
typedef struct { |
uint32_t sym; |
uint32_t target; |
uint32_t flags; |
} runtime_pseudo_reloc_item_v2; |
|
typedef struct { |
uint32_t magic1; |
uint32_t magic2; |
uint32_t version; |
} runtime_pseudo_reloc_v2; |
|
#define RP_VERSION_V1 0 |
#define RP_VERSION_V2 1 |
|
static void |
do_pseudo_reloc (void * start, void * end, void * base) |
{ |
ptrdiff_t addr_imp, reldata; |
ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start); |
runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start; |
runtime_pseudo_reloc_item_v2 *r; |
|
/* A valid relocation list will contain at least one entry, and |
* one v1 data structure (the smallest one) requires two DWORDs. |
* So, if the relocation list is smaller than 8 bytes, bail. |
*/ |
if (reloc_target < 8) |
return; |
|
/* Check if this is the old pseudo relocation version. */ |
/* There are two kinds of v1 relocation lists: |
* 1) With a (v2-style) version header. In this case, the |
* first entry in the list is a 3-DWORD structure, with |
* value: |
* { 0, 0, RP_VERSION_V1 } |
* In this case, we skip to the next entry in the list, |
* knowing that all elements after the head item can |
* be cast to runtime_pseudo_reloc_item_v1. |
* 2) Without a (v2-style) version header. In this case, the |
* first element in the list IS an actual v1 relocation |
* record, which is two DWORDs. Because there will never |
* be a case where a v1 relocation record has both |
* addend == 0 and target == 0, this case will not be |
* confused with the prior one. |
* All current binutils, when generating a v1 relocation list, |
* use the second (e.g. original) form -- that is, without the |
* v2-style version header. |
*/ |
if (reloc_target >= 12 |
&& v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0 |
&& v2_hdr->version == RP_VERSION_V1) |
{ |
/* We have a list header item indicating that the rest |
* of the list contains v1 entries. Move the pointer to |
* the first true v1 relocation record. By definition, |
* that v1 element will not have both addend == 0 and |
* target == 0 (and thus, when interpreted as a |
* runtime_pseudo_reloc_v2, it will not have both |
* magic1 == 0 and magic2 == 0). |
*/ |
v2_hdr++; |
} |
|
if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0) |
{ |
/************************* |
* Handle v1 relocations * |
*************************/ |
runtime_pseudo_reloc_item_v1 * o; |
for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr; |
o < (runtime_pseudo_reloc_item_v1 *)end; |
o++) |
{ |
uint32_t newval; |
reloc_target = (ptrdiff_t) base + o->target; |
newval = (*((uint32_t*) reloc_target)) + o->addend; |
*(uint32_t*)reloc_target = newval; |
} |
return; |
} |
|
/* If we got this far, then we have relocations of version 2 or newer */ |
|
/* Check if this is a known version. */ |
if (v2_hdr->version != RP_VERSION_V2) |
{ |
printf(" Unknown pseudo relocation protocol version %d.\n", |
(int) v2_hdr->version); |
return; |
} |
|
/************************* |
* Handle v2 relocations * |
*************************/ |
|
/* Walk over header. */ |
r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1]; |
|
for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++) |
{ |
/* location where new address will be written */ |
reloc_target = (ptrdiff_t) base + r->target; |
|
/* get sym pointer. It points either to the iat entry |
* of the referenced element, or to the stub function. |
*/ |
addr_imp = (ptrdiff_t) base + r->sym; |
addr_imp = *((ptrdiff_t *) addr_imp); |
|
/* read existing relocation value from image, casting to the |
* bitsize indicated by the 8 LSBs of flags. If the value is |
* negative, manually sign-extend to ptrdiff_t width. Raise an |
* error if the bitsize indicated by the 8 LSBs of flags is not |
* supported. |
*/ |
switch ((r->flags & 0xff)) |
{ |
case 8: |
reldata = (ptrdiff_t) (*((unsigned char *)reloc_target)); |
if ((reldata & 0x80) != 0) |
reldata |= ~((ptrdiff_t) 0xff); |
break; |
case 16: |
reldata = (ptrdiff_t) (*((unsigned short *)reloc_target)); |
if ((reldata & 0x8000) != 0) |
reldata |= ~((ptrdiff_t) 0xffff); |
break; |
case 32: |
reldata = (ptrdiff_t) (*((unsigned int *)reloc_target)); |
break; |
default: |
reldata=0; |
printf(" Unknown pseudo relocation bit size %d.\n", |
(int) (r->flags & 0xff)); |
break; |
} |
|
/* Adjust the relocation value */ |
reldata -= ((ptrdiff_t) base + r->sym); |
reldata += addr_imp; |
|
/* Write the new relocation value back to *reloc_target */ |
switch ((r->flags & 0xff)) |
{ |
case 8: |
*(uint8_t*)reloc_target = (uint8_t)reldata; |
break; |
case 16: |
*(uint16_t*)reloc_target = (uint16_t)reldata; |
break; |
case 32: |
*(uint32_t*)reloc_target = (uint32_t)reldata; |
break; |
} |
} |
} |
|
void |
_pei386_runtime_relocator (void) |
{ |
static int was_init = 0; |
if (was_init) |
return; |
++was_init; |
do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__, |
&__RUNTIME_PSEUDO_RELOC_LIST_END__, |
&_image_base__); |
} |