Commit 2c987e28 authored by David 'Digit' Turner's avatar David 'Digit' Turner Committed by Commit Bot

android: crazy_linker: Add GNU hash table support.

The static linker currently generates two symbol hash tables by
default in each ELF binary (executables or shared libraries).
One uses the old, slow and large SysV format, and the other the
much smaller and much faster GNU format.

This CL adds support for the GNU format to the crazy linker
library (which turns out to require very little code).

This will allow the removal of the SysV table whenever the
chromium linker is being used (i.e. for ChromePublic.apk and
ChromeModernPublic.apk and their derivatives).

This will also be handy when we split libchrome.so into
several native libraries in the future (e.g. to support
android app feature modules like VR), which will require
exporting a lot more symbols.

Note that MonochromePublic.apk, which only runs on Android N+
already uses GNU tables exclusively (for the record, support
for this format was added in Android M / API level 23).

+ Add unit-tests. And scripts to generate the test data from
  a known list of symbols.

+ Add a small script to regenerate all auto-generated sources
  at once.

BUG=851391
R=pasko@chromium.org, rmcilroy@chromium.org, agrieve@chromium.org, cjgrant@chromium.org

Change-Id: I42b1aac46445b5b4d4b3b4f69eaa69ea47e7ab3c
Reviewed-on: https://chromium-review.googlesource.com/1216122
Commit-Queue: David Turner <digit@chromium.org>
Reviewed-by: default avataragrieve <agrieve@chromium.org>
Reviewed-by: default avatarEgor Pasko <pasko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#590291}
parent 7fddb926
......@@ -33,6 +33,7 @@ if (is_android) {
":crazy_linker_test_load_library",
":crazy_linker_test_load_library_callbacks",
":crazy_linker_test_load_library_depends",
":crazy_linker_test_load_library_with_gnu_hash_table",
":crazy_linker_test_relocated_shared_relro",
":crazy_linker_test_search_path_list",
":crazy_linker_test_shared_relro",
......@@ -58,6 +59,8 @@ if (is_android) {
"src/src/crazy_linker_debug.cpp",
"src/src/crazy_linker_debug.h",
"src/src/crazy_linker_defines.h",
"src/src/crazy_linker_elf_hash_table.cpp",
"src/src/crazy_linker_elf_hash_table.h",
"src/src/crazy_linker_elf_loader.cpp",
"src/src/crazy_linker_elf_loader.h",
"src/src/crazy_linker_elf_relocations.cpp",
......@@ -72,6 +75,8 @@ if (is_android) {
"src/src/crazy_linker_error.h",
"src/src/crazy_linker_globals.cpp",
"src/src/crazy_linker_globals.h",
"src/src/crazy_linker_gnu_hash_table.cpp",
"src/src/crazy_linker_gnu_hash_table.h",
"src/src/crazy_linker_library_list.cpp",
"src/src/crazy_linker_library_list.h",
"src/src/crazy_linker_library_view.cpp",
......@@ -145,8 +150,11 @@ if (is_android) {
test("android_crazy_linker_unittests") {
sources = [
"src/src/crazy_linker_ashmem_unittest.cpp",
"src/src/crazy_linker_elf_hash_table_unittest.cpp",
"src/src/crazy_linker_elf_symbols_unittest.cpp",
"src/src/crazy_linker_error_unittest.cpp",
"src/src/crazy_linker_globals_unittest.cpp",
"src/src/crazy_linker_gnu_hash_table_unittest.cpp",
"src/src/crazy_linker_line_reader_unittest.cpp",
"src/src/crazy_linker_pointer_set_unittest.cpp",
"src/src/crazy_linker_proc_maps_unittest.cpp",
......@@ -178,20 +186,18 @@ if (is_android) {
template("crazy_linker_test_library") {
shared_library(target_name) {
forward_variables_from(invoker,
[
"deps",
"data_deps",
"defines",
"ldflags",
])
sources = invoker.sources
libs = [ "log" ]
if (defined(invoker.deps)) {
deps = invoker.deps
}
if (defined(invoker.data_deps)) {
data_deps = invoker.data_deps
}
if (defined(invoker.libs)) {
libs += invoker.libs
}
if (defined(invoker.defines)) {
defines = invoker.defines
}
# This is not Chromium code.
configs -= [ "//build/config/compiler:chromium_code" ]
......@@ -212,6 +218,13 @@ if (is_android) {
]
}
crazy_linker_test_library("crazy_linker_tests_libfoo_with_gnu_hash_table") {
sources = [
"src/tests/foo.cpp",
]
ldflags = [ "-Wl,--hash-style=gnu" ]
}
crazy_linker_test_library("crazy_linker_tests_libfoo2") {
sources = [
"src/tests/foo2.cpp",
......@@ -367,6 +380,20 @@ if (is_android) {
]
}
executable("crazy_linker_test_load_library_with_gnu_hash_table") {
sources = [
"src/tests/test_load_library.cpp",
]
defines =
[ "LIB_NAME=\"libcrazy_linker_tests_libfoo_with_gnu_hash_table.so\"" ]
data_deps = [
":crazy_linker_tests_libfoo_with_gnu_hash_table",
]
deps = [
":android_crazy_linker",
]
}
executable("crazy_linker_test_dl_wrappers") {
sources = [
"src/tests/test_dl_wrappers.cpp",
......
......@@ -143,6 +143,7 @@ libcrazy_linker_tests_libbar_with_two_dlopens.so \
libcrazy_linker_tests_libfoo.so \
libcrazy_linker_tests_libfoo_with_relro.so \
libcrazy_linker_tests_libfoo_with_static_constructor.so \
libcrazy_linker_tests_libfoo_with_gnu_hash_table.so \
libcrazy_linker_tests_libfoo2.so \
libcrazy_linker_tests_libzoo.so \
libcrazy_linker_tests_libzoo_dlopen_in_initializer.so \
......@@ -161,6 +162,7 @@ crazy_linker_test_dl_wrappers_with_system_handle \
crazy_linker_test_dl_wrappers_valid_handles \
crazy_linker_test_load_library \
crazy_linker_test_load_library_depends \
crazy_linker_test_load_library_with_gnu_hash_table \
crazy_linker_test_relocated_shared_relro \
crazy_linker_test_search_path_list \
crazy_linker_test_shared_relro \
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crazy_linker_elf_hash_table.h"
#include <string.h>
namespace crazy {
// Compute the ELF hash of a given symbol.
static unsigned ElfHash(const char* name) {
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(name);
unsigned h = 0;
while (*ptr) {
h = (h << 4) + *ptr++;
unsigned g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
void ElfHashTable::Init(uintptr_t dt_elf_hash) {
const ELF::Word* data = reinterpret_cast<const ELF::Word*>(dt_elf_hash);
hash_bucket_size_ = data[0];
hash_bucket_ = data + 2;
hash_chain_size_ = data[1];
hash_chain_ = hash_bucket_ + hash_bucket_size_;
}
bool ElfHashTable::IsValid() const {
return hash_bucket_size_ > 0;
}
const ELF::Sym* ElfHashTable::LookupByName(const char* symbol_name,
const ELF::Sym* symbol_table,
const char* string_table) const {
unsigned hash = ElfHash(symbol_name);
for (unsigned n = hash_bucket_[hash % hash_bucket_size_]; n != 0;
n = hash_chain_[n]) {
const ELF::Sym* symbol = &symbol_table[n];
// Check that the symbol has the appropriate name.
if (!strcmp(string_table + symbol->st_name, symbol_name))
return symbol;
}
return nullptr;
}
} // namespace crazy
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRAZY_LINKER_ELF_HASH_TABLE_H
#define CRAZY_LINKER_ELF_HASH_TABLE_H
#include <stddef.h>
#include "elf_traits.h"
namespace crazy {
// Models the hash table used to map symbol names to symbol entries using
// the standard ELF format.
class ElfHashTable {
public:
// Initialize instance. |dt_elf_hash| should be the address that the
// DT_HASH entry points to in the input ELF dynamic section. Call IsValid()
// to determine whether the table was well-formed.
void Init(uintptr_t dt_elf_hash);
// Returns true iff the content of the table is valid.
bool IsValid() const;
// Index of the first dynamic symbol within the ELF symbol table.
size_t dyn_symbols_offset() const { return 1; }
// Number of dynamic symbols in the ELF symbol table.
size_t dyn_symbols_count() const { return hash_chain_size_ - 1; }
// Lookup |symbol_name| in the table. |symbol_table| should point to the
// ELF symbol table, and |string_table| to the start of its string table.
// Returns nullptr on failure.
const ELF::Sym* LookupByName(const char* symbol_name,
const ELF::Sym* symbol_table,
const char* string_table) const;
private:
const ELF::Word* hash_bucket_ = nullptr;
size_t hash_bucket_size_ = 0;
const ELF::Word* hash_chain_ = nullptr;
size_t hash_chain_size_ = 0;
};
} // namespace crazy
#endif // CRAZY_LINKER_ELF_HASH_TABLE_H
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRAZY_LINKER_ELF_HASH_TABLE_TEST_DATA_H
#define CRAZY_LINKER_ELF_HASH_TABLE_TEST_DATA_H
// clang-format off
// BEGIN_AUTO_GENERATED [generate_test_elf_hash_tables.py] DO NOT EDIT!!
//
namespace crazy {
namespace testing {
// SysV ELF hash table: num_buckets=4 num_chain=16
//
// idx symbol hash bucket chain
// 0 <STN_UNDEF>
// 1 isnan 0070a47e 2 5
// 2 freelocal 0bc334fc 0 4
// 3 hcreate_ 0a8b8c4f 3 6
// 4 getopt_long_onl 0f256dbc 0 12
// 5 endrpcen 04b96f7e 2 7
// 6 pthread_mutex_lock 0de6a18b 3 0
// 7 isinf 0070a046 2 9
// 8 setrlimi 0cb929a9 1 11
// 9 getspen 0dcba6de 2 10
// 10 umoun 007c46be 2 13
// 11 strsigna 0b99fbe1 1 0
// 12 listxatt 00abef84 0 15
// 13 gettyen 0dcbbfde 2 14
// 14 uselib 07c9c2f2 2 0
// 15 cfsetispeed 0b63b274 0 0
//
// Buckets: 2, 8, 1, 3
//
static const char kTestElfStringTable[145] = {
'\0','i','s','n','a','n','\0','f','r','e','e','l','o','c','a','l','\0',
'h','c','r','e','a','t','e','_','\0','g','e','t','o','p','t','_','l','o',
'n','g','_','o','n','l','\0','e','n','d','r','p','c','e','n','\0','p',
't','h','r','e','a','d','_','m','u','t','e','x','_','l','o','c','k','\0',
'i','s','i','n','f','\0','s','e','t','r','l','i','m','i','\0','g','e',
't','s','p','e','n','\0','u','m','o','u','n','\0','s','t','r','s','i',
'g','n','a','\0','l','i','s','t','x','a','t','t','\0','g','e','t','t',
'y','e','n','\0','u','s','e','l','i','b','\0','c','f','s','e','t','i',
's','p','e','e','d','\0','\0'};
// Auto-generated macro used to list all symbols
// XX must be a macro that takes the following parameters:
// name: symbol name (quoted).
// str_offset: symbol name offset in string table
// address: virtual address.
// size: size in bytes
#define LIST_ELF_SYMBOLS_TestElf(XX) \
XX("isnan", 1, 0x10000, 16) \
XX("freelocal", 7, 0x10020, 16) \
XX("hcreate_", 17, 0x10040, 16) \
XX("getopt_long_onl", 26, 0x10060, 16) \
XX("endrpcen", 42, 0x10080, 16) \
XX("pthread_mutex_lock", 51, 0x100a0, 16) \
XX("isinf", 70, 0x100c0, 16) \
XX("setrlimi", 76, 0x100e0, 16) \
XX("getspen", 85, 0x10100, 16) \
XX("umoun", 93, 0x10120, 16) \
XX("strsigna", 99, 0x10140, 16) \
XX("listxatt", 108, 0x10160, 16) \
XX("gettyen", 117, 0x10180, 16) \
XX("uselib", 125, 0x101a0, 16) \
XX("cfsetispeed", 132, 0x101c0, 16) \
// END OF LIST
// NOTE: ELF32_Sym and ELF64_Sym have very different layout.
#if UINTPTR_MAX == UINT32_MAX // ELF32_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), (address), (size), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */ },
#else // ELF64_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */, (address), (size) },
#endif // !ELF64_Sym
static const ELF::Sym kTestElfSymbolTable[] = {
{ 0 }, // ST_UNDEF
LIST_ELF_SYMBOLS_TestElf(DEFINE_ELF_SYMBOL)
};
#undef DEFINE_ELF_SYMBOL
static const uint32_t kTestElfHashTable[] = {
4, // num_buckets
16, // num_chain
// Buckets
0x00000002, 0x00000008, 0x00000001, 0x00000003,
// Chain
0x00000000, 0x00000005, 0x00000004, 0x00000006, 0x0000000c, 0x00000007,
0x00000000, 0x00000009, 0x0000000b, 0x0000000a, 0x0000000d, 0x00000000,
0x0000000f, 0x0000000e, 0x00000000, 0x00000000,
};
} // namespace testing
} // namespace crazy
// END_AUTO_GENERATED_CODE [generate_test_elf_hash_tables.py]
// clang-format on
#endif // CRAZY_LINKER_ELF_HASH_TABLE_TEST_DATA_H
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crazy_linker_elf_hash_table.h"
#include <gtest/gtest.h>
#include "crazy_linker_elf_hash_table_test_data.h"
#include <stdint.h>
namespace crazy {
namespace testing {
TEST(ElfHashTable, LookupByName) {
ElfHashTable table;
table.Init(reinterpret_cast<uintptr_t>(&kTestElfHashTable));
EXPECT_TRUE(table.IsValid());
const ELF::Sym* sym;
#define CHECK_SYMBOL(name, offset, address, size) \
sym = table.LookupByName(name, kTestElfSymbolTable, kTestElfStringTable); \
EXPECT_TRUE(sym) << name; \
EXPECT_EQ((address), sym->st_value) << name; \
EXPECT_EQ((size), sym->st_size) << name;
LIST_ELF_SYMBOLS_TestElf(CHECK_SYMBOL);
#undef CHECK_SYMBOL
// Check a few undefined symbols.
EXPECT_FALSE(table.LookupByName("ahahahahah", kTestElfSymbolTable,
kTestElfStringTable));
EXPECT_FALSE(
table.LookupByName("strsign", kTestElfSymbolTable, kTestElfStringTable));
}
TEST(ElfHashTable, DynSymbols) {
ElfHashTable table;
table.Init(reinterpret_cast<uintptr_t>(&kTestElfHashTable));
EXPECT_TRUE(table.IsValid());
const size_t kExpectedOffset = 1U;
const size_t kExpectedCount =
(sizeof(kTestElfSymbolTable) / sizeof(kTestElfSymbolTable[0])) - 1U;
EXPECT_EQ(kExpectedOffset, table.dyn_symbols_offset());
EXPECT_EQ(kExpectedCount, table.dyn_symbols_count());
}
} // namespace testing
} // namespace crazy
......@@ -9,22 +9,21 @@
namespace crazy {
namespace {
// Compute the ELF hash of a given symbol.
unsigned ElfHash(const char* name) {
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(name);
unsigned h = 0;
while (*ptr) {
h = (h << 4) + *ptr++;
unsigned g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
ElfSymbols::ElfSymbols(const ELF::Sym* symbol_table,
const char* string_table,
uintptr_t dt_elf_hash,
uintptr_t dt_gnu_hash)
: symbol_table_(symbol_table), string_table_(string_table) {
if (dt_elf_hash)
elf_hash_.Init(dt_elf_hash);
if (dt_gnu_hash)
gnu_hash_.Init(dt_gnu_hash);
}
} // namespace
bool ElfSymbols::IsValid() const {
return (symbol_table_ && string_table_ &&
(gnu_hash_.IsValid() || elf_hash_.IsValid()));
}
bool ElfSymbols::Init(const ElfView* view) {
LOG("Parsing dynamic table");
......@@ -34,13 +33,11 @@ bool ElfSymbols::Init(const ElfView* view) {
switch (dyn.GetTag()) {
case DT_HASH:
LOG(" DT_HASH addr=%p", dyn_addr);
{
ELF::Word* data = reinterpret_cast<ELF::Word*>(dyn_addr);
hash_bucket_size_ = data[0];
hash_chain_size_ = data[1];
hash_bucket_ = data + 2;
hash_chain_ = data + 2 + hash_bucket_size_;
}
elf_hash_.Init(dyn_addr);
break;
case DT_GNU_HASH:
LOG(" DT_GNU_HASH addr=%p", dyn_addr);
gnu_hash_.Init(dyn_addr);
break;
case DT_STRTAB:
LOG(" DT_STRTAB addr=%p", dyn_addr);
......@@ -54,10 +51,7 @@ bool ElfSymbols::Init(const ElfView* view) {
;
}
}
if (symbol_table_ == NULL || string_table_ == NULL || hash_bucket_ == NULL)
return false;
return true;
return IsValid();
}
const ELF::Sym* ElfSymbols::LookupByAddress(void* address,
......@@ -65,14 +59,13 @@ const ELF::Sym* ElfSymbols::LookupByAddress(void* address,
ELF::Addr elf_addr =
reinterpret_cast<ELF::Addr>(address) - static_cast<ELF::Addr>(load_bias);
for (size_t n = 0; n < hash_chain_size_; ++n) {
const ELF::Sym* sym = &symbol_table_[n];
if (sym->st_shndx != SHN_UNDEF && elf_addr >= sym->st_value &&
elf_addr < sym->st_value + sym->st_size) {
return sym;
for (const ELF::Sym& sym : GetDynSymbols()) {
if (sym.st_shndx != SHN_UNDEF && elf_addr >= sym.st_value &&
elf_addr < sym.st_value + sym.st_size) {
return &sym;
}
}
return NULL;
return nullptr;
}
bool ElfSymbols::LookupNearestByAddress(void* address,
......@@ -83,29 +76,28 @@ bool ElfSymbols::LookupNearestByAddress(void* address,
ELF::Addr elf_addr =
reinterpret_cast<ELF::Addr>(address) - static_cast<ELF::Addr>(load_bias);
const ELF::Sym* nearest_sym = NULL;
const ELF::Sym* nearest_sym = nullptr;
size_t nearest_diff = ~size_t(0);
for (size_t n = 0; n < hash_chain_size_; ++n) {
const ELF::Sym* sym = &symbol_table_[n];
if (sym->st_shndx == SHN_UNDEF)
for (const ELF::Sym& sym : GetDynSymbols()) {
if (sym.st_shndx == SHN_UNDEF)
continue;
if (elf_addr >= sym->st_value && elf_addr < sym->st_value + sym->st_size) {
if (elf_addr >= sym.st_value && elf_addr < sym.st_value + sym.st_size) {
// This is a perfect match.
nearest_sym = sym;
nearest_sym = &sym;
break;
}
// Otherwise, compute distance.
size_t diff;
if (elf_addr < sym->st_value)
diff = sym->st_value - elf_addr;
if (elf_addr < sym.st_value)
diff = sym.st_value - elf_addr;
else
diff = elf_addr - sym->st_value - sym->st_size;
diff = elf_addr - sym.st_value - sym.st_size;
if (diff < nearest_diff) {
nearest_sym = sym;
nearest_sym = &sym;
nearest_diff = diff;
}
}
......@@ -120,27 +112,30 @@ bool ElfSymbols::LookupNearestByAddress(void* address,
}
const ELF::Sym* ElfSymbols::LookupByName(const char* symbol_name) const {
unsigned hash = ElfHash(symbol_name);
const ELF::Sym* sym =
gnu_hash_.IsValid()
? gnu_hash_.LookupByName(symbol_name, symbol_table_, string_table_)
: elf_hash_.LookupByName(symbol_name, symbol_table_, string_table_);
for (unsigned n = hash_bucket_[hash % hash_bucket_size_]; n != 0;
n = hash_chain_[n]) {
const ELF::Sym* symbol = &symbol_table_[n];
// Check that the symbol has the appropriate name.
if (strcmp(string_table_ + symbol->st_name, symbol_name))
continue;
// Ignore undefined symbols.
if (symbol->st_shndx == SHN_UNDEF)
continue;
// Ignore anything that isn't a global or weak definition.
switch (ELF_ST_BIND(symbol->st_info)) {
case STB_GLOBAL:
case STB_WEAK:
return symbol;
default:
;
}
// Ignore undefined symbols or those that are not global or weak definitions.
if (!sym || sym->st_shndx == SHN_UNDEF)
return nullptr;
uint8_t info = ELF_ST_BIND(sym->st_info);
if (info != STB_GLOBAL && info != STB_WEAK)
return nullptr;
return sym;
}
ElfSymbols::DynSymbols ElfSymbols::GetDynSymbols() const {
if (gnu_hash_.IsValid()) {
return {symbol_table_, gnu_hash_.dyn_symbols_offset(),
gnu_hash_.dyn_symbols_count()};
} else {
return {symbol_table_, elf_hash_.dyn_symbols_offset(),
elf_hash_.dyn_symbols_count()};
}
return NULL;
}
} // namespace crazy
......@@ -5,10 +5,12 @@
#ifndef CRAZY_LINKER_ELF_SYMBOLS_H
#define CRAZY_LINKER_ELF_SYMBOLS_H
#include <string.h>
#include "crazy_linker_elf_hash_table.h"
#include "crazy_linker_gnu_hash_table.h"
#include "elf_traits.h"
#include <string.h>
namespace crazy {
class ElfView;
......@@ -17,17 +19,31 @@ class ElfView;
// binary.
class ElfSymbols {
public:
ElfSymbols() { ::memset(this, 0, sizeof(*this)); }
~ElfSymbols() {}
ElfSymbols() = default;
// Constructor used for unit-testing.
ElfSymbols(const ELF::Sym* symbol_table,
const char* string_table,
uintptr_t dt_elf_hash,
uintptr_t dt_gnu_hash);
// Returns true iff instance is valid.
bool IsValid() const;
// Initializes instance from |view|. Returns true on success, or false if
// the ELF image is malformed.
bool Init(const ElfView* view);
// Returns the symbol table entry associated with |symbol_name|, or nullptr.
const ELF::Sym* LookupByName(const char* symbol_name) const;
// Returns the symbol table entry associated with |symbol_id|.
const ELF::Sym* LookupById(size_t symbol_id) const {
return &symbol_table_[symbol_id];
}
// Returns the symbol table entry corresponding to a given |address|.
// |load_bias| must be the ELF image's load bias. Return nullptr if not found.
const ELF::Sym* LookupByAddress(void* address, size_t load_bias) const;
// Returns true iff symbol with id |symbol_id| is weak.
......@@ -35,41 +51,59 @@ class ElfSymbols {
return ELF_ST_BIND(symbol_table_[symbol_id].st_info) == STB_WEAK;
}
// Returns the name of the symbol associated with |symbol_id|.
const char* LookupNameById(size_t symbol_id) const {
const ELF::Sym* sym = LookupById(symbol_id);
if (!sym)
return NULL;
return nullptr;
return string_table_ + sym->st_name;
}
// Returns the address of the symbol identified by |symbol_name|. |load_bias|
// must be the ELF image's load bias. Return nullptr if not found.
void* LookupAddressByName(const char* symbol_name, size_t load_bias) const {
const ELF::Sym* sym = LookupByName(symbol_name);
if (!sym)
return NULL;
return nullptr;
return reinterpret_cast<void*>(load_bias + sym->st_value);
}
// Lookups symbol information that is nearest to |address|, where |load_bias|
// is the ELF image load bias. On success, return true and set |*sym_name|,
// |*sym_addre| and |*sym_size|. On failure, return false.
bool LookupNearestByAddress(void* address,
size_t load_bias,
const char** sym_name,
void** sym_addr,
size_t* sym_size) const;
// Returns string identified by |str_id|.
const char* GetStringById(size_t str_id) const {
return string_table_ + str_id;
}
// TODO(digit): Remove this once ElfRelocator is gone.
const ELF::Sym* symbol_table() const { return symbol_table_; }
const char* string_table() const { return string_table_; }
private:
const ELF::Sym* symbol_table_;
const char* string_table_;
ELF::Word* hash_bucket_;
size_t hash_bucket_size_;
ELF::Word* hash_chain_;
size_t hash_chain_size_;
// Simple range view for the dynamic symbols within |symbol_table_|.
// Provides begin() and end() to allow for-range loops.
class DynSymbols {
public:
DynSymbols(const ELF::Sym* symbols, size_t start, size_t count)
: begin_(symbols + start), end_(symbols + start + count) {}
const ELF::Sym* begin() const { return begin_; }
const ELF::Sym* end() const { return end_; }
private:
const ELF::Sym* begin_;
const ELF::Sym* end_;
};
DynSymbols GetDynSymbols() const;
const ELF::Sym* symbol_table_ = nullptr;
const char* string_table_ = nullptr;
ElfHashTable elf_hash_ = {};
GnuHashTable gnu_hash_ = {};
};
} // namespace crazy
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crazy_linker_elf_symbols.h"
#include <gtest/gtest.h>
#include <memory>
#include "crazy_linker_elf_hash_table_test_data.h"
#include "crazy_linker_gnu_hash_table_test_data.h"
namespace crazy {
namespace testing {
class ElfSymbolsTest : public ::testing::Test {
protected:
ElfSymbolsTest(const ELF::Sym* symbol_table,
const char* string_table,
uintptr_t dt_elf_hash,
uintptr_t dt_gnu_hash)
: symbols_(symbol_table, string_table, dt_elf_hash, dt_gnu_hash) {}
ElfSymbols symbols_;
};
class ElfSymbolsElfHashTest : public ElfSymbolsTest {
public:
ElfSymbolsElfHashTest()
: ElfSymbolsTest(kTestElfSymbolTable,
kTestElfStringTable,
reinterpret_cast<uintptr_t>(kTestElfHashTable),
0) {}
};
class ElfSymbolsGnuHashTest : public ElfSymbolsTest {
public:
ElfSymbolsGnuHashTest()
: ElfSymbolsTest(kTestGnuSymbolTable,
kTestGnuStringTable,
0,
reinterpret_cast<uintptr_t>(kTestGnuHashTable)) {}
};
#define CHECK_SYMBOL_BY_NAME(name, offset, address, size) \
sym = symbols_.LookupByName(name); \
EXPECT_TRUE(sym) << name; \
EXPECT_EQ((address), sym->st_value) << name; \
EXPECT_EQ((size), sym->st_size) << name;
#define CHECK_SYMBOL_BY_ADDRESS(name, offset, address, size) \
sym = symbols_.LookupByAddress(reinterpret_cast<void*>(address), 0); \
EXPECT_TRUE(sym) << name; \
EXPECT_STREQ((name), symbols_.string_table() + sym->st_name) << name; \
EXPECT_EQ((address), sym->st_value) << name;
#define CHECK_SYMBOL_BY_NEAREST_ADDRESS(name, offset, address, size) \
EXPECT_TRUE( \
symbols_.LookupNearestByAddress(reinterpret_cast<void*>((address)-2), 0, \
&sym_name, &sym_addr, &sym_size)) \
<< name; \
EXPECT_STREQ((name), sym_name) << name; \
EXPECT_EQ((address), reinterpret_cast<uintptr_t>(sym_addr)) << name; \
EXPECT_EQ((size), sym_size) << name;
TEST_F(ElfSymbolsElfHashTest, LookupByName) {
const ELF::Sym* sym;
LIST_ELF_SYMBOLS_TestElf(CHECK_SYMBOL_BY_NAME);
}
TEST_F(ElfSymbolsElfHashTest, LookupByAddress) {
const ELF::Sym* sym;
LIST_ELF_SYMBOLS_TestElf(CHECK_SYMBOL_BY_ADDRESS);
}
TEST_F(ElfSymbolsElfHashTest, LookupNearestByAddress) {
const char* sym_name;
void* sym_addr;
size_t sym_size;
LIST_ELF_SYMBOLS_TestElf(CHECK_SYMBOL_BY_NEAREST_ADDRESS);
}
TEST_F(ElfSymbolsGnuHashTest, LookupByName) {
const ELF::Sym* sym;
LIST_ELF_SYMBOLS_TestGnu(CHECK_SYMBOL_BY_NAME);
}
TEST_F(ElfSymbolsGnuHashTest, LookupByAddress) {
const ELF::Sym* sym;
LIST_ELF_SYMBOLS_TestGnu(CHECK_SYMBOL_BY_ADDRESS);
}
TEST_F(ElfSymbolsGnuHashTest, LookupNearestByAddress) {
const char* sym_name;
void* sym_addr;
size_t sym_size;
LIST_ELF_SYMBOLS_TestGnu(CHECK_SYMBOL_BY_NEAREST_ADDRESS);
}
} // namespace testing
} // namespace crazy
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crazy_linker_gnu_hash_table.h"
#include <string.h>
namespace crazy {
// Compute the GNU hash of a given symbol.
static uint32_t GnuHash(const char* name) {
uint32_t h = 5381;
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(name);
while (*ptr) {
h = h * 33 + *ptr++;
}
return h;
}
void GnuHashTable::Init(uintptr_t dt_gnu_hash) {
sym_count_ = 0;
const uint32_t* data = reinterpret_cast<const uint32_t*>(dt_gnu_hash);
num_buckets_ = data[0];
sym_offset_ = data[1];
if (!num_buckets_)
return;
const uint32_t bloom_size = data[2];
if ((bloom_size & (bloom_size - 1U)) != 0) // must be a power of 2
return;
bloom_word_mask_ = bloom_size - 1U;
bloom_shift_ = data[3];
bloom_filter_ = reinterpret_cast<const ELF::Addr*>(data + 4);
buckets_ = reinterpret_cast<const uint32_t*>(bloom_filter_ + bloom_size);
chain_ = buckets_ + num_buckets_;
// Compute number of dynamic symbols by parsing the table.
if (num_buckets_ > 0) {
// First find the maximum index in the buckets table.
uint32_t max_index = buckets_[0];
for (size_t n = 1; n < num_buckets_; ++n) {
uint32_t sym_index = buckets_[n];
if (sym_index > max_index)
max_index = sym_index;
}
// Now start to look at the chain_ table from (max_index - sym_offset_)
// until there is a value with LSB set to 1, indicating the end of the
// last chain.
while ((chain_[max_index - sym_offset_] & 1) == 0)
max_index++;
sym_count_ = (max_index - sym_offset_) + 1;
}
}
bool GnuHashTable::IsValid() const {
return sym_count_ > 0;
}
const ELF::Sym* GnuHashTable::LookupByName(const char* symbol_name,
const ELF::Sym* symbol_table,
const char* string_table) const {
uint32_t hash = GnuHash(symbol_name);
// First, bloom filter test.
const unsigned kElfBits = ELF::kElfBits;
ELF::Addr word = bloom_filter_[(hash / kElfBits) & bloom_word_mask_];
ELF::Addr mask = (ELF::Addr(1) << (hash % kElfBits)) |
(ELF::Addr(1) << ((hash >> bloom_shift_) % kElfBits));
if ((word & mask) != mask)
return nullptr;
uint32_t sym_index = buckets_[hash % num_buckets_];
if (sym_index < sym_offset_)
return nullptr;
while (true) {
const ELF::Sym* sym = symbol_table + sym_index;
const uint32_t sym_hash = chain_[sym_index - sym_offset_];
const char* sym_name = string_table + sym->st_name;
if ((sym_hash | 1) == (hash | 1) && !strcmp(sym_name, symbol_name)) {
return sym;
}
if (sym_hash & 1)
break;
sym_index++;
}
return nullptr;
}
} // namespace crazy
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRAZY_LINKER_GNU_HASH_TABLE_H
#define CRAZY_LINKER_GNU_HASH_TABLE_H
#include <stddef.h>
#include "elf_traits.h"
namespace crazy {
// Models the hash table used to map symbol names to symbol entries using
// the GNU format. This one is smaller and faster than the standard ELF one.
class GnuHashTable {
public:
// Initialize instance. |dt_gnu_hash| should be the address that the
// DT_GNU_HASH entry points to in the input ELF dynamic section. Call
// IsValid() to determine whether the table was well-formed.
void Init(uintptr_t dt_gnu_hash);
// Returns true iff the content of the table is valid.
bool IsValid() const;
// Return the index of the first dynamic symbol within the ELF symbol table.
size_t dyn_symbols_offset() const { return sym_offset_; };
// Number of dynamic symbols in the ELF symbol table.
size_t dyn_symbols_count() const { return sym_count_; }
// Lookup |symbol_name| in the table. |symbol_table| should point to the
// ELF symbol table, and |string_table| to the start of its string table.
// Returns nullptr on failure.
const ELF::Sym* LookupByName(const char* symbol_name,
const ELF::Sym* symbol_table,
const char* string_table) const;
private:
uint32_t num_buckets_ = 0;
uint32_t sym_offset_ = 0;
uint32_t sym_count_ = 0;
uint32_t bloom_word_mask_ = 0;
uint32_t bloom_shift_ = 0;
const ELF::Addr* bloom_filter_ = nullptr;
const uint32_t* buckets_ = nullptr;
const uint32_t* chain_ = nullptr;
};
} // namespace crazy
#endif // CRAZY_LINKER_GNU_HASH_TABLE_H
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRAZY_LINKER_GNU_HASH_TABLE_TEST_DATA_H
#define CRAZY_LINKER_GNU_HASH_TABLE_TEST_DATA_H
// clang-format off
// BEGIN_AUTO_GENERATED [generate_test_gnu_hash_tables.py] DO NOT EDIT!!
//
namespace crazy {
namespace testing {
// GNU hash table: num_buckets=4 bloom_size=2 bloom_shift=5
//
// idx symbol hash bucket bloom32 bloom64 chain
//
// 0 ST_UNDEF
// 1 cfsetispeed 830acc54 0 0:20:02 1:20:34 830acc54
// 2 strsigna 90f1e4b0 0 1:16:05 0:48:37 90f1e4b0
// 3 hcreate_ 4c7e3240 0 0:00:18 1:00:18 4c7e3240
// 4 endrpcen b6c44714 0 0:20:24 0:20:56 b6c44715
// 5 uselib 2124d3e9 1 1:09:31 1:41:31 2124d3e8
// 6 gettyen f07bdd25 1 1:05:09 0:37:41 f07bdd24
// 7 umoun 1081e019 1 0:25:00 0:25:00 1081e019
// 8 freelocal e3364372 2 1:18:27 1:50:27 e3364372
// 9 listxatt ced3d862 2 1:02:03 1:34:03 ced3d862
// 10 isnan 0fabfd7e 2 1:30:11 1:62:43 0fabfd7e
// 11 isinf 0fabe9de 2 0:30:14 1:30:14 0fabe9de
// 12 setrlimi 12e23bae 2 1:14:29 0:46:29 12e23baf
// 13 getspen f07b2a7b 3 1:27:19 1:59:19 f07b2a7a
// 14 pthread_mutex_lock 4f152227 3 1:07:17 0:39:17 4f152226
// 15 getopt_long_onl 57b1584f 3 0:15:02 1:15:02 57b1584f
//
// Buckets: 1, 5, 8, 13
//
// Bloom filter (32 bits):
// bit# 24 16 8 0
// .x....xx ...x.x.. xx...... .....x.x
// xxx.x... ....xxxx .x..x.x. x.x.xx..
//
// also as: 0x4314c005 0xe80f4aac
//
// Bloom filter (64 bits):
// bit# 56 48 40 32 24 16 8 0
// .......x .......x .x....x. x.x..... ..x...x. ...x..x. ........ .......x
// .x..x... .....x.. ....x.x. .....x.. xx..x... ...xxx.. xx...... ....xx.x
//
// also as: 0x010142a022120001 0x48040a04c81cc00d
//
static const char kTestGnuStringTable[145] = {
'\0','c','f','s','e','t','i','s','p','e','e','d','\0','s','t','r','s',
'i','g','n','a','\0','h','c','r','e','a','t','e','_','\0','e','n','d',
'r','p','c','e','n','\0','u','s','e','l','i','b','\0','g','e','t','t',
'y','e','n','\0','u','m','o','u','n','\0','f','r','e','e','l','o','c',
'a','l','\0','l','i','s','t','x','a','t','t','\0','i','s','n','a','n',
'\0','i','s','i','n','f','\0','s','e','t','r','l','i','m','i','\0','g',
'e','t','s','p','e','n','\0','p','t','h','r','e','a','d','_','m','u','t',
'e','x','_','l','o','c','k','\0','g','e','t','o','p','t','_','l','o','n',
'g','_','o','n','l','\0','\0'};
// Auto-generated macro used to list all symbols
// XX must be a macro that takes the following parameters:
// name: symbol name (quoted).
// str_offset: symbol name offset in string table
// address: virtual address.
// size: size in bytes
#define LIST_ELF_SYMBOLS_TestGnu(XX) \
XX("cfsetispeed", 1, 0x10000, 16) \
XX("strsigna", 13, 0x10020, 16) \
XX("hcreate_", 22, 0x10040, 16) \
XX("endrpcen", 31, 0x10060, 16) \
XX("uselib", 40, 0x10080, 16) \
XX("gettyen", 47, 0x100a0, 16) \
XX("umoun", 55, 0x100c0, 16) \
XX("freelocal", 61, 0x100e0, 16) \
XX("listxatt", 71, 0x10100, 16) \
XX("isnan", 80, 0x10120, 16) \
XX("isinf", 86, 0x10140, 16) \
XX("setrlimi", 92, 0x10160, 16) \
XX("getspen", 101, 0x10180, 16) \
XX("pthread_mutex_lock", 109, 0x101a0, 16) \
XX("getopt_long_onl", 128, 0x101c0, 16) \
// END OF LIST
// NOTE: ELF32_Sym and ELF64_Sym have very different layout.
#if UINTPTR_MAX == UINT32_MAX // ELF32_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), (address), (size), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */ },
#else // ELF64_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */, (address), (size) },
#endif // !ELF64_Sym
static const ELF::Sym kTestGnuSymbolTable[] = {
{ 0 }, // ST_UNDEF
LIST_ELF_SYMBOLS_TestGnu(DEFINE_ELF_SYMBOL)
};
#undef DEFINE_ELF_SYMBOL
static const uint32_t kTestGnuHashTable[] = {
4, // num_buckets
1, // sym_offset
2, // bloom_size
5, // bloom_shift
// Bloom filter words
#if UINTPTR_MAX == UINT32_MAX // 32-bit bloom filter words
0x4314c005, 0xe80f4aac,
#else // 64-bits filter bloom words (assumes little-endianess)
0x22120001, 0x010142a0, 0xc81cc00d, 0x48040a04,
#endif // bloom filter words
// Buckets
0x00000001, 0x00000005, 0x00000008, 0x0000000d,
// Chain
0x830acc54, 0x90f1e4b0, 0x4c7e3240, 0xb6c44715, 0x2124d3e8, 0xf07bdd24,
0x1081e019, 0xe3364372, 0xced3d862, 0x0fabfd7e, 0x0fabe9de, 0x12e23baf,
0xf07b2a7a, 0x4f152226, 0x57b1584f,
};
} // namespace testing
} // namespace crazy
// END_AUTO_GENERATED_CODE [generate_test_gnu_hash_tables.py]
// clang-format on
#endif // CRAZY_LINKER_GNU_HASH_TABLE_TEST_DATA_H
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crazy_linker_gnu_hash_table.h"
#include <gtest/gtest.h>
#include "crazy_linker_gnu_hash_table_test_data.h"
#include <stdint.h>
namespace crazy {
namespace testing {
TEST(GnuHashTable, LookupByName) {
GnuHashTable table;
table.Init(reinterpret_cast<uintptr_t>(&kTestGnuHashTable));
EXPECT_TRUE(table.IsValid());
const ELF::Sym* sym;
#define CHECK_SYMBOL(name, offset, address, size) \
sym = table.LookupByName(name, kTestGnuSymbolTable, kTestGnuStringTable); \
EXPECT_TRUE(sym) << name; \
EXPECT_EQ((address), sym->st_value) << name; \
EXPECT_EQ((size), sym->st_size) << name;
LIST_ELF_SYMBOLS_TestGnu(CHECK_SYMBOL);
#undef CHECK_SYMBOL
// Check a few undefined symbols.
EXPECT_FALSE(table.LookupByName("ahahahahah", kTestGnuSymbolTable,
kTestGnuStringTable));
EXPECT_FALSE(
table.LookupByName("strsign", kTestGnuSymbolTable, kTestGnuStringTable));
}
TEST(GnuHashTable, DynSymbols) {
GnuHashTable table;
table.Init(reinterpret_cast<uintptr_t>(&kTestGnuHashTable));
EXPECT_TRUE(table.IsValid());
const size_t kExpectedOffset = kTestGnuHashTable[1];
const size_t kExpectedCount =
(sizeof(kTestGnuSymbolTable) / sizeof(kTestGnuSymbolTable[0])) -
kExpectedOffset;
EXPECT_EQ(kExpectedOffset, table.dyn_symbols_offset());
EXPECT_EQ(kExpectedCount, table.dyn_symbols_count());
}
} // namespace testing
} // namespace crazy
#!/usr/bin/env python
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Simple script used to generate the SysV ELF hash table test data"""
import collections
import os
from pylib import source_utils
from pylib import elf_utils
script_name = os.path.basename(__file__)
def ElfHash(name):
"""Compute the ELF hash of a given input string."""
h = 0
for c in name:
h = (h << 4) + ord(c)
g = h & 0xf0000000
h ^= g
h ^= g >> 24
return h & 0xffffffff
class ElfHashTable(object):
def __init__(self, num_buckets, symbol_names):
"""Initialize a new SysV ELF hash table instance.
Args:
num_buckets: Number of hash buckets, must be > 0.
symbol_names: List of symbol names.
"""
self.num_buckets_ = num_buckets
self.num_chain_ = len(symbol_names) + 1
self.symbols_ = symbol_names
self.hashes_ = [ElfHash(t) for t in symbol_names]
# Build bucket and chain arrays.
buckets = [0] * num_buckets
chain = [0] * self.num_chain_
for n, symbol in enumerate(self.symbols_):
elf_hash = self.hashes_[n]
bucket_index = elf_hash % num_buckets
idx = buckets[bucket_index]
if idx == 0:
buckets[bucket_index] = n + 1
else:
while chain[idx] != 0:
idx = chain[idx]
chain[idx] = n + 1
self.buckets_ = buckets
self.chain_ = chain
# Generate final string table and symbol offsets.
self.string_table_, self.symbol_offsets_ = \
elf_utils.GenerateStringTable(self.symbols_)
def __str__(self):
"""Dump human-friendly text description for this table."""
out = 'SysV ELF hash table: num_buckets=%d num_chain=%d\n\n' % (
self.num_buckets_, self.num_chain_)
out += 'idx symbol hash bucket chain\n'
out += ' 0 <STN_UNDEF>\n'
for n, symbol in enumerate(self.symbols_):
elf_hash = self.hashes_[n]
bucket_index = elf_hash % self.num_buckets_
out += '%3d %-20s %08x %-3d %d\n' % (
n + 1, symbol, elf_hash, bucket_index, self.chain_[n + 1])
out += '\nBuckets: '
comma = ''
for b in self.buckets_:
out += '%s%d' % (comma, b)
comma = ', '
out += '\n'
return out
def AsCSource(self, variable_prefix, guard_macro_name):
"""Dump the content of this instance."""
out = source_utils.CSourceBeginAutoGeneratedHeader(script_name,
guard_macro_name)
out += source_utils.CSourceForComments(str(self))
out += source_utils.CSourceForConstCharArray(
self.string_table_, 'k%sStringTable' % variable_prefix)
out += '\n'
out += elf_utils.CSourceForElfSymbolListMacro(variable_prefix,
self.symbols_,
self.symbol_offsets_)
out += '\n'
out += elf_utils.CSourceForElfSymbolTable(variable_prefix,
self.symbols_,
self.symbol_offsets_)
out += '\nstatic const uint32_t k%sHashTable[] = {\n' % variable_prefix
out += ' %d, // num_buckets\n' % self.num_buckets_
out += ' %d, // num_chain\n' % self.num_chain_
out += ' // Buckets\n'
out += source_utils.CSourceForIntegerHexArray(self.buckets_, 32)
out += ' // Chain\n'
out += source_utils.CSourceForIntegerHexArray(self.chain_, 32)
out += '};\n'
out += source_utils.CSourceEndAutoGeneratedHeader(script_name,
guard_macro_name)
return out
def main():
# Same data as the one found on the following web page:
#
# https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/
#
# NOTE: The hash values on that page are incorrect, so results differs!!
#
table = ElfHashTable(4, [
'isnan', 'freelocal', 'hcreate_', 'getopt_long_onl', 'endrpcen',
'pthread_mutex_lock', 'isinf', 'setrlimi', 'getspen', 'umoun',
'strsigna', 'listxatt', 'gettyen', 'uselib', 'cfsetispeed'])
print table.AsCSource('TestElf', 'CRAZY_LINKER_ELF_HASH_TABLE_TEST_DATA_H')
if __name__ == "__main__":
main()
#!/usr/bin/env python
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Simple script used to generate the GNU hash table test data"""
import collections
import os
from pylib import source_utils
from pylib import elf_utils
script_name = os.path.basename(__file__)
def GnuHash(name):
"""Compute the GNU hash of a given input string."""
h = 5381
for c in name:
h = (h * 33 + ord(c)) & 0xffffffff
return h
class BloomFilter(object):
"""A class used to model the bloom filter used in GNU hash tables.
Usage is the following:
1) Create new instance.
2) Call Add() repeatedly to add new entries for each symbol hash value.
3) Call AsCSourceUint32Array() to generate a C source fragment
corresponding to the content of an array of 32-bit words for the
filter.
4) Also use __str__() to print a human-friendly representation of the
filter to check everything if needed.
"""
def __init__(self, bloom_size, bloom_shift, bloom_bits):
"""Create instance.
Args:
bloom_size: number of words in the bloom filter.
bloom_shift: bloom bit shift to use for secondary bit.
bloom_bits: number of bits in each filter word (32 or 64).
"""
self.bloom_size_ = bloom_size
self.bloom_shift_ = bloom_shift
self.bloom_bits_ = bloom_bits
self.bloom_filter_ = [0] * bloom_size
def GetBits(self, gnu_hash):
"""Return (index, bit0, bit1) tuple corresponding to a given hash."""
bloom_index = (gnu_hash / self.bloom_bits_) % self.bloom_size_
bloom_bit0 = (gnu_hash % self.bloom_bits_)
bloom_bit1 = ((gnu_hash >> self.bloom_shift_) % self.bloom_bits_)
return bloom_index, bloom_bit0, bloom_bit1
def Add(self, gnu_hash):
"""Add a new entry to the filter."""
word, bit0, bit1 = self.GetBits(gnu_hash)
self.bloom_filter_[word] |= (1 << bit0) | (1 << bit1)
def AsCSourceUint32Array(self):
"""Generate C source fragment for 32-bit uint array data."""
if self.bloom_bits_ == 64:
# Convert to array of 32-bit values first. Assume little-endianess.
values = []
for bloom in self.bloom_filter_:
values += [bloom & 0xffffffff, (bloom >> 32) % 0xffffffff]
else:
values = self.bloom_filter_
return source_utils.CSourceForIntegerHexArray(values, 32)
def __str__(self):
"""Convert bloom filter instance to human-friendly representation."""
out = 'Bloom filter (%d bits):\n' % self.bloom_bits_
out += 'bit#'
if self.bloom_bits_ == 64:
out += ' 56 48 40 32'
out += ' 24 16 8 0'
for bloom in self.bloom_filter_:
for n in range(self.bloom_bits_):
if (n % 8) == 0:
if n > 0:
out += ' '
else:
out += '\n '
if ((bloom & (1 << (self.bloom_bits_ - n - 1))) != 0):
out += 'x'
else:
out += '.'
out += '\n\n also as: '
for bloom in self.bloom_filter_:
if self.bloom_bits_ == 64:
out += ' 0x%016x' % bloom
else:
out += ' 0x%08x' % bloom
out += '\n'
return out
class GnuHashTable(object):
def __init__(self, sym_offset, num_buckets, bloom_size, bloom_shift,
bloom_bits, symbol_names):
"""Initialize a new GNU hash table instance.
Args:
sym_offset: Dynamic symbols offset, must be > 0.
num_buckets: Number of hash buckets, must be > 0.
bloom_size: Bloom filter size in words of |bloom_bits| bits.
bloom_shift: Bloom filter shift.
bloom_bits: Either 32 or 64, size of bloom filter words.
symbol_names: List of symbol names.
"""
self.num_buckets_ = num_buckets
self.sym_offset_ = sym_offset
self.bloom_size_ = bloom_size
self.bloom_shift_ = bloom_shift
# Create a list of (symbol, hash) values, sorted by increasing bucket index
sorted_symbols = sorted([(x, GnuHash(x)) for x in symbol_names],
key=lambda t: t[1] % num_buckets)
self.symbols_ = [t[0] for t in sorted_symbols]
self.hashes_ = [t[1] for t in sorted_symbols]
# Build bucket and chain arrays.
buckets = [0] * num_buckets
chain = [0] * len(sorted_symbols)
last_bucket_index = -1
for n, symbol in enumerate(sorted_symbols):
gnu_hash = self.hashes_[n]
bucket_index = gnu_hash % num_buckets
if bucket_index != last_bucket_index:
buckets[bucket_index] = n + sym_offset
last_bucket_index = bucket_index
if n > 0: chain[n - 1] |= 1
chain[n] = gnu_hash & ~1
if chain: chain[-1] |= 1
self.buckets_ = buckets
self.chain_ = chain
# Generate bloom filters for both 32 and 64 bits.
self.bloom_filter32_ = self._GenerateBloomFilter(32)
self.bloom_filter64_ = self._GenerateBloomFilter(64)
# Generate final string table and symbol offsets.
self.string_table_, self.symbol_offsets_ = \
elf_utils.GenerateStringTable(self.symbols_)
def _GenerateBloomFilter(self, bloom_bits):
"""Generate bloom filter array for a specific bitness."""
bloom = BloomFilter(self.bloom_size_, self.bloom_shift_, bloom_bits)
for gnu_hash in self.hashes_:
bloom.Add(gnu_hash)
return bloom
def __str__(self):
"""Human friendly text description for this table."""
out = 'GNU hash table: num_buckets=%d bloom_size=%d bloom_shift=%d\n\n' % (
self.num_buckets_, self.bloom_size_, self.bloom_shift_)
out += 'idx symbol hash bucket bloom32 bloom64 chain\n\n'
out += ' 0 ST_UNDEF\n'
for n, symbol in enumerate(self.symbols_):
gnu_hash = self.hashes_[n]
bucket_index = gnu_hash % self.num_buckets_
bloom32_index, bloom32_bit0, bloom32_bit1 = self.bloom_filter32_.GetBits(gnu_hash)
bloom64_index, bloom64_bit0, bloom64_bit1 = self.bloom_filter64_.GetBits(gnu_hash)
out += '%3d %-20s %08x %-3d %d:%02d:%02d %d:%02d:%02d %08x\n' % (
n + 1, symbol, gnu_hash, bucket_index, bloom32_index, bloom32_bit0,
bloom32_bit1, bloom64_index, bloom64_bit0, bloom64_bit1,
self.chain_[n])
out += '\nBuckets: '
comma = ''
for b in self.buckets_:
out += '%s%d' % (comma, b)
comma = ', '
out += '\n\n%s\n%s' % (self.bloom_filter32_, self.bloom_filter64_)
return out
def AsCSource(self, variable_prefix, guard_macro_name):
"""Dump the content of this instance."""
out = source_utils.CSourceBeginAutoGeneratedHeader(script_name,
guard_macro_name)
out += source_utils.CSourceForComments(str(self))
out += source_utils.CSourceForConstCharArray(
self.string_table_, 'k%sStringTable' % variable_prefix)
out += '\n'
out += elf_utils.CSourceForElfSymbolListMacro(variable_prefix,
self.symbols_,
self.symbol_offsets_)
out += '\n'
out += elf_utils.CSourceForElfSymbolTable(variable_prefix,
self.symbols_,
self.symbol_offsets_)
out += '\nstatic const uint32_t k%sHashTable[] = {\n' % variable_prefix
out += ' %d, // num_buckets\n' % self.num_buckets_
out += ' %d, // sym_offset\n' % self.sym_offset_
out += ' %d, // bloom_size\n' % self.bloom_size_
out += ' %d, // bloom_shift\n' % self.bloom_shift_
out += ' // Bloom filter words\n'
out += '#if UINTPTR_MAX == UINT32_MAX // 32-bit bloom filter words\n'
out += self.bloom_filter32_.AsCSourceUint32Array()
out += '#else // 64-bits filter bloom words (assumes little-endianess)\n'
out += self.bloom_filter64_.AsCSourceUint32Array()
out += '#endif // bloom filter words\n'
out += ' // Buckets\n'
out += source_utils.CSourceForIntegerHexArray(self.buckets_, 32)
out += ' // Chain\n'
out += source_utils.CSourceForIntegerHexArray(self.chain_, 32)
out += '};\n'
out += source_utils.CSourceEndAutoGeneratedHeader(script_name,
guard_macro_name)
return out
def main():
# Same data as the one found on the following web page, to ease verification:
#
# https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/
#
# NOTE: The bloom filter values and bitmaps on that page are widely incorrect
# but the bloom word and bit indices are ok though!
#
table = GnuHashTable(1, 4, 2, 5, 64, [
'cfsetispeed', 'strsigna', 'hcreate_', 'endrpcen', 'uselib',
'gettyen', 'umoun', 'freelocal', 'listxatt', 'isnan', 'isinf',
'setrlimi', 'getspen', 'pthread_mutex_lock', 'getopt_long_onl',
])
print table.AsCSource('TestGnu', 'CRAZY_LINKER_GNU_HASH_TABLE_TEST_DATA_H')
if __name__ == "__main__":
main()
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Common ELF related routines."""
def GenerateStringTable(symbol_names):
"""Generate the string table that corresponds to a list of symbol names.
Args:
symbol_names: List of input symbol names.
Returns:
A (string_table, symbol_offsets) tuple, where |string_table| is the
actual string table (terminated by two '\0' chars), and |symbol_offsets|
is a list of starting offsets for each symbol inside the table.
"""
symbol_offsets = []
string_table = "\0"
next_offset = 1
for symbol in symbol_names:
symbol_offsets.append(next_offset)
string_table += symbol
string_table += "\0"
next_offset += len(symbol) + 1
string_table += "\0"
return string_table, symbol_offsets
def CSourceForElfSymbolTable(variable_prefix, names, str_offsets):
"""Generate C source definition for an ELF symbol table.
Args:
variable_prefix: variable name prefix
names: List of symbol names.
str_offsets: List of symbol name offsets in string table.
Returns:
String containing C source fragment.
"""
out = (
r'''// NOTE: ELF32_Sym and ELF64_Sym have very different layout.
#if UINTPTR_MAX == UINT32_MAX // ELF32_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), (address), (size), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */ },
#else // ELF64_Sym
# define DEFINE_ELF_SYMBOL(name, name_offset, address, size) \
{ (name_offset), ELF_ST_INFO(STB_GLOBAL, STT_FUNC), \
0 /* other */, 1 /* shndx */, (address), (size) },
#endif // !ELF64_Sym
''')
out += 'static const ELF::Sym k%sSymbolTable[] = {\n' % variable_prefix
out += ' { 0 }, // ST_UNDEF\n'
out += ' LIST_ELF_SYMBOLS_%s(DEFINE_ELF_SYMBOL)\n' % variable_prefix
out += '};\n'
out += '#undef DEFINE_ELF_SYMBOL\n'
return out
def CSourceForElfSymbolListMacro(variable_prefix, names, name_offsets,
base_address=0x10000, symbol_size=16,
spacing_size=16):
"""Generate C source definition for a macro listing ELF symbols.
Args:
macro_suffix: Macro name suffix.
names: List of symbol names.
name_offsets: List of symbol offsets.
base_address: Base starting address for symbols,
symbol_size: Symbol size in bytes (all have the same size).
spacing_size: Additionnal bytes between symbols.
Returns:
String containing C source fragment.
"""
out = (
r'''// Auto-generated macro used to list all symbols
// XX must be a macro that takes the following parameters:
// name: symbol name (quoted).
// str_offset: symbol name offset in string table
// address: virtual address.
// size: size in bytes
''')
out += '#define LIST_ELF_SYMBOLS_%s(XX) \\\n' % variable_prefix
address = base_address
for sym, offset in zip(names, name_offsets):
out += ' XX("%s", %d, 0x%x, %d) \\\n' % (
sym, offset, address, symbol_size)
address += symbol_size + spacing_size
out += ' // END OF LIST\n'
return out
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Common utility functions to generate C++ source fragments."""
_AUTO_GENERATED_HEADER_BEGIN_TEMPLATE = (
r'''// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef {guard_macro_name}
#define {guard_macro_name}
// clang-format off
// BEGIN_AUTO_GENERATED [{script_name}] DO NOT EDIT!!
//
namespace crazy {{
namespace testing {{
''')
_AUTO_GENERATED_HEADER_END_TEMPLATE = (
r'''
}} // namespace testing
}} // namespace crazy
// END_AUTO_GENERATED_CODE [{script_name}]
// clang-format on
#endif // {guard_macro_name}'''
)
def CSourceForIntegerHexArray(values, num_bits, margin=4, width=80):
"""Turn an array of integers into a C source array data definition.
Args:
values: An array of integers.
num_bits: The number of bits of said integers (i.e. 8, 16, 32 or 64).
margin: Left-side margin / indentation level (must be > 0).
width: Maximum line width.
Returns:
A string containing the data definition as a C source fragment.
"""
chars_per_word = num_bits / 4
# Account for 0x prefix + one space + one comma
max_items_per_line = (width - margin - 2) / (4 + chars_per_word)
line_start = ' ' * (margin - 1)
slen = len(values)
n = 0
format_str = ' 0x%%0%dx,' % chars_per_word
out = ''
while n < slen:
out += line_start
n2 = min(n + max_items_per_line, slen)
while n < n2:
out += format_str % values[n]
n += 1
out += '\n';
return out
def CSourceForConstCharArray(chars, variable_name, margin=4, width=80):
"""Return C source fragment for static const char C array.
Args:
chars: An array or string containing all the characters for array.
variable_name: Name of the array variable.
Returns:
A new string holding a C source fragment for the array definition.
"""
slen = len(chars)
out = 'static const char %s[%d] = {\n' % (variable_name, slen)
line_start = ' ' * margin
# Allow for margin + 4 spaces on the right.
max_width = width - margin - 4
rpos = 0
wpos = margin + 1
while rpos < slen:
out += line_start
wpos = 0
comma = ''
while rpos < slen and wpos < max_width:
ch = chars[rpos]
code = ord(ch)
if code < 32 or code > 127:
ch = "'\\%d'" % code
else:
ch = "'%s'" % ch
ch = comma + ch
if wpos + len(ch) > max_width: # Too long, break line before this char.
break
out += ch
rpos += 1
wpos += len(ch)
comma = ','
if rpos == slen:
break
out += ',\n'
out += '};\n'
return out
def CSourceForComments(lines):
"""Wrap the content of |lines| instead into C++ comments."""
out = ''
for line in lines.split('\n'):
out += '// %s\n' % line
return out
def CSourceBeginAutoGeneratedHeader(script_name, guard_macro_name):
return _AUTO_GENERATED_HEADER_BEGIN_TEMPLATE.format(
script_name=script_name,
guard_macro_name=guard_macro_name)
def CSourceEndAutoGeneratedHeader(script_name, guard_macro_name):
return _AUTO_GENERATED_HEADER_END_TEMPLATE.format(
script_name=script_name,
guard_macro_name=guard_macro_name)
......@@ -15,7 +15,9 @@
typedef void (*FunctionPtr)();
#ifndef LIB_NAME
#define LIB_NAME "libcrazy_linker_tests_libfoo.so"
#endif
int main() {
crazy_context_t* context = crazy_context_create();
......
#!/bin/sh
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# A simple script used to regenerate all sources that are normally
# auto-generated from scripts located under tests/
set -e
export LANG=C
export LC_ALL=C
PROGDIR=$(dirname "$0")
cd "$PROGDIR"
# Do not run this every time for now, because the script hard-codes the current
# date in each generated archive, making the content change on every call.
# TODO(digit): Fix this.
# tests/generate_zip_test_tables.sh > src/crazy_linker_zip_test_data.cpp
tests/generate_test_elf_hash_tables.py > \
src/crazy_linker_elf_hash_table_test_data.h
tests/generate_test_gnu_hash_tables.py > \
src/crazy_linker_gnu_hash_table_test_data.h
echo "Done"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment