Bug 1747782 - Use a more compact representation of relocations in elfhack. r=gsvelto

Use the SHT_RELR format which significantly improves the size reduction
from elfhack:

   total size of .rel.* + .elfhack.* sections

                       x86      x86_64   android-arm
   plain             3532904   10739544    3488888
   current-elfhack   1085815    1155994    1042048
   relr-elfhack       130219     193552     113840

Differential Revision: https://phabricator.services.mozilla.com/D134756
This commit is contained in:
Mike Hommey
2021-12-31 01:32:52 +00:00
parent 546bf5b141
commit b02b96fd8a
3 changed files with 114 additions and 58 deletions

View File

@@ -10,7 +10,7 @@
#include "elfxx.h"
#include "mozilla/CheckedInt.h"
#define ver "0"
#define ver "1"
#define elfhack_data ".elfhack.data.v" ver
#define elfhack_text ".elfhack.text.v" ver
@@ -47,42 +47,66 @@ class Elf_Addr_Traits {
typedef serializable<Elf_Addr_Traits> Elf_Addr;
class Elf_RelHack_Traits {
public:
typedef Elf32_Rel Type32;
typedef Elf32_Rel Type64;
template <class endian, typename R, typename T>
static inline void swap(T& t, R& r) {
r.r_offset = endian::swap(t.r_offset);
r.r_info = endian::swap(t.r_info);
}
};
typedef serializable<Elf_RelHack_Traits> Elf_RelHack;
class ElfRelHack_Section : public ElfSection {
public:
ElfRelHack_Section(Elf_Shdr& s) : ElfSection(s, nullptr, nullptr) {
ElfRelHack_Section(Elf_Shdr& s)
: ElfSection(s, nullptr, nullptr),
block_size((8 * s.sh_entsize - 1) * s.sh_entsize) {
name = elfhack_data;
};
void serialize(std::ofstream& file, unsigned char ei_class,
unsigned char ei_data) {
for (std::vector<Elf_RelHack>::iterator i = rels.begin(); i != rels.end();
++i)
(*i).serialize(file, ei_class, ei_data);
for (std::vector<Elf64_Addr>::iterator i = relr.begin(); i != relr.end();
++i) {
Elf_Addr out;
out.value = *i;
out.serialize(file, ei_class, ei_data);
}
}
bool isRelocatable() { return true; }
void push_back(Elf_RelHack& r) {
rels.push_back(r);
shdr.sh_size = rels.size() * shdr.sh_entsize;
void push_back(Elf64_Addr offset) {
// The format used for the packed relocations is SHT_RELR, described in
// https://groups.google.com/g/generic-abi/c/bX460iggiKg/m/Jnz1lgLJAgAJ
// The gist of it is that an address is recorded, and the following words,
// if their LSB is 1, represent a bitmap of word-size-spaced relocations
// at the addresses that follow. There can be multiple such bitmaps, such
// that very long streaks of (possibly spaced) relocations can be recorded
// in a very compact way.
for (;;) {
// [block_start; block_start + block_size] represents the range of offsets
// the current bitmap can record. If the offset doesn't fall in that
// range, or if doesn't align properly to be recorded, we record the
// bitmap, and slide the block corresponding to a new bitmap. If the
// offset doesn't fall in the range for the new bitmap, or if there wasn't
// an active bitmap in the first place, we record the offset and start a
// new bitmap for the block that follows it.
if (!block_start || offset < block_start ||
offset >= block_start + block_size ||
(offset - block_start) % shdr.sh_entsize) {
if (bitmap) {
relr.push_back((bitmap << 1) | 1);
block_start += block_size;
bitmap = 0;
continue;
}
relr.push_back(offset);
block_start = offset + shdr.sh_entsize;
break;
}
bitmap |= 1ULL << ((offset - block_start) / shdr.sh_entsize);
break;
}
shdr.sh_size = relr.size() * shdr.sh_entsize;
}
private:
std::vector<Elf_RelHack> rels;
std::vector<Elf64_Addr> relr;
size_t block_size;
Elf64_Addr block_start = 0;
Elf64_Addr bitmap = 0;
};
class ElfRelHackCode_Section : public ElfSection {
@@ -859,18 +883,16 @@ int do_relocation_section(Elf* elf, unsigned int rel_type,
}
assert(section->getType() == Rel_Type::sh_type);
Elf64_Shdr relhack64_section = {
0,
SHT_PROGBITS,
SHF_ALLOC,
0,
(Elf64_Off)-1LL,
0,
SHN_UNDEF,
0,
Elf_RelHack::size(elf->getClass()),
Elf_RelHack::size(elf->getClass())}; // TODO: sh_addralign should be an
// alignment, not size
Elf64_Shdr relhack64_section = {0,
SHT_PROGBITS,
SHF_ALLOC,
0,
(Elf64_Off)-1LL,
0,
SHN_UNDEF,
0,
Elf_Addr::size(elf->getClass()),
Elf_Addr::size(elf->getClass())};
Elf64_Shdr relhackcode64_section = {0,
SHT_PROGBITS,
SHF_ALLOC | SHF_EXECINSTR,
@@ -915,8 +937,6 @@ int do_relocation_section(Elf* elf, unsigned int rel_type,
Elf_SymValue* sym = symtab->lookup("__cxa_pure_virtual");
std::vector<Rel_Type> new_rels;
Elf_RelHack relhack_entry;
relhack_entry.r_offset = relhack_entry.r_info = 0;
std::vector<Rel_Type> init_array_relocs;
size_t init_array_insert = 0;
for (typename std::vector<Rel_Type>::iterator i = section->rels.begin();
@@ -965,6 +985,10 @@ int do_relocation_section(Elf* elf, unsigned int rel_type,
// Don't pack relocations happening in non writable sections.
// Our injected code is likely not to be allowed to write there.
new_rels.push_back(*i);
} else if (i->r_offset & 1) {
// RELR packing doesn't support relocations at an odd address, but
// there shouldn't be any.
new_rels.push_back(*i);
} else {
// With Elf_Rel, the value pointed by the relocation offset is the addend.
// With Elf_Rela, the addend is in the relocation entry, but the elfhacked
@@ -983,20 +1007,11 @@ int do_relocation_section(Elf* elf, unsigned int rel_type,
"Relocation addend inconsistent with content. Skipping\n");
return -1;
}
if (i->r_offset ==
relhack_entry.r_offset + relhack_entry.r_info * entry_sz) {
relhack_entry.r_info++;
} else {
if (relhack_entry.r_offset) relhack->push_back(relhack_entry);
relhack_entry.r_offset = i->r_offset;
relhack_entry.r_info = 1;
}
relhack->push_back(i->r_offset);
}
}
if (relhack_entry.r_offset) relhack->push_back(relhack_entry);
// Last entry must be nullptr
relhack_entry.r_offset = relhack_entry.r_info = 0;
relhack->push_back(relhack_entry);
// Last entry must be a nullptr
relhack->push_back(0);
if (init_array) {
// Some linkers create a DT_INIT_ARRAY section that, for all purposes,

View File

@@ -49,7 +49,7 @@ extern __attribute__((visibility("hidden"))) void original_init(int argc,
char** argv,
char** env);
extern __attribute__((visibility("hidden"))) Elf32_Rel relhack[];
extern __attribute__((visibility("hidden"))) Elf_Addr relhack[];
extern __attribute__((visibility("hidden"))) Elf_Ehdr elf_header;
extern __attribute__((visibility("hidden"))) int (*mprotect_cb)(void* addr,
@@ -60,12 +60,24 @@ extern __attribute__((visibility("hidden"))) char relro_start[];
extern __attribute__((visibility("hidden"))) char relro_end[];
static inline __attribute__((always_inline)) void do_relocations(void) {
Elf32_Rel* rel;
Elf_Addr *ptr, *start;
for (rel = relhack; rel->r_offset; rel++) {
start = (Elf_Addr*)((intptr_t)&elf_header + rel->r_offset);
for (ptr = start; ptr < &start[rel->r_info]; ptr++)
Elf_Addr* ptr;
for (Elf_Addr* entry = relhack; *entry; entry++) {
if ((*entry & 1) == 0) {
ptr = (Elf_Addr*)((intptr_t)&elf_header + *entry);
*ptr += (intptr_t)&elf_header;
} else {
size_t remaining = (8 * sizeof(Elf_Addr) - 1);
Elf_Addr bits = *entry;
do {
bits >>= 1;
remaining--;
ptr++;
if (bits & 1) {
*ptr += (intptr_t)&elf_header;
}
} while (bits);
ptr += remaining;
}
}
}

View File

@@ -101,7 +101,7 @@ const char* strings[] = {
};
/* Create a hole between two zones of relative relocations */
const int hole[] = {42, 42, 42, 42};
int small_hole[] = {42, 42, 42, 42};
const char* strings2[] = {
# include "test.c"
@@ -109,6 +109,27 @@ const char* strings2[] = {
# include "test.c"
# include "test.c"
# include "test.c"
};
/* Create a bigger hole between two zones of relative relocations */
int bigger_hole[] = {
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
};
const char* strings3[] = {
# include "test.c"
# include "test.c"
# include "test.c"
# include "test.c"
# include "test.c"
# undef DEF
};
@@ -145,7 +166,7 @@ void end_test() {
}
void test() {
int i = 0, j = 0;
int i = 0, j = 0, k = 0;
# define DEF_(a, i, w) \
if (a[i++] != str_##w) return;
# define DEF(w) DEF_(strings, i, w)
@@ -159,9 +180,17 @@ void test() {
# include "test.c"
# include "test.c"
# include "test.c"
# undef DEF
# define DEF(w) DEF_(strings3, k, w)
# include "test.c"
# include "test.c"
# include "test.c"
# include "test.c"
# include "test.c"
# undef DEF
if (i != sizeof(strings) / sizeof(strings[0]) &&
j != sizeof(strings2) / sizeof(strings2[0]))
j != sizeof(strings2) / sizeof(strings2[0]) &&
k != sizeof(strings3) / sizeof(strings3[0]))
fprintf(stderr, "WARNING: Test doesn't cover the whole array\n");
end_test();
}