Skip to content

Commit

Permalink
Merge .riscv.attributes input sections
Browse files Browse the repository at this point in the history
  • Loading branch information
rui314 committed Jul 29, 2023
1 parent 1cbe4d4 commit aa64491
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 3 deletions.
247 changes: 246 additions & 1 deletion elf/arch-riscv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,250 @@ i64 riscv_resize_sections(Context<E> &ctx) {
return set_osec_offsets(ctx);
}

// ISA name handlers
//
// An example of ISA name is "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0".
// An ISA name starts with the base name (e.g. "rv64i2p1") followed by
// ISA extensions separated by underscores.
//
// There are lots of ISA extensions defined for RISC-V, and they are
// identified by name. Some extensions are of single-letter alphabet such
// as "m" or "q". Newer extension names start with "z" followed by one or
// more alphabets (i.e. "zicsr"). "s" and "x" prefixes are reserved
// for supervisor- level extensions and private extensions, respectively.
//
// Each extension consists of a name, a major version and a minor version.
// For example, "m2p0" indicates the "m" extension of version 2.0. "p" is
// just a separator.
//
// Each RISC-V object file contains an ISA string enumerating extensions
// used by the object file. We need to merge input objects' ISA strings
// into a single ISA string.
//
// In order to guarantee string uniqueness, extensions have to be ordered
// in a specific manner. The exact rule is unfortunately a bit complicated.
//
// The following functions takes care of ISA strings.

struct Extn {
std::string_view name;
i64 major;
i64 minor;
};

// As per the RISC-V spec, the extension names must be sorted in a very
// specific way, and unfortunately that's not just an alphabetical order.
// For example, rv64imafd is a legal ISA string, whereas rv64iafdm is not.
// The exact rule is somewhat arbitrary.
//
// This function returns true if the first extension name should precede
// the second one as per the rule.
static bool extn_name_less(const Extn &e1, const Extn &e2) {
auto get_single_letter_rank = [](char c) -> i64 {
std::string_view exts = "iemafdqlcbkjtpvnh";
size_t pos = exts.find_first_of(c);
if (pos != exts.npos)
return pos;
return c - 'a' + exts.size();
};

auto get_rank = [&](std::string_view str) -> i64 {
switch (str[0]) {
case 'x':
return 1 << 20;
case 's':
return 1 << 19;
case 'z':
return (1 << 18) + get_single_letter_rank(str[1]);
default:
return get_single_letter_rank(str[0]);
}
};

return std::tuple{get_rank(e1.name), e1.name} <
std::tuple{get_rank(e2.name), e2.name};
}

static bool extn_version_less(const Extn &e1, const Extn &e2) {
return std::tuple{e1.major, e1.minor} <
std::tuple{e2.major, e2.minor};
}

static std::optional<Extn> read_extn_string(std::string_view &str) {
Extn extn;

size_t pos = str.find_first_of("0123456789");
if (pos == str.npos)
return {};

extn.name = str.substr(0, pos);
str = str.substr(pos);

size_t nread;
extn.major = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.size() < 2 || str[0] != 'p')
return {};
str = str.substr(1);

extn.minor = std::stoul(std::string(str), &nread, 10);
str = str.substr(nread);
if (str.empty() || str[0] == '_')
return extn;
return {};
}

static std::vector<Extn> parse_arch_string(std::string_view str) {
if (str.size() < 5)
return {};

// Parse the base part
std::string_view base = str.substr(0, 5);
if (base != "rv32i" && base != "rv32e" && base != "rv64i" && base != "rv64e")
return {};
str = str.substr(4);

std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};

std::vector<Extn> vec;
extn->name = base;
vec.push_back(*extn);

// Parse extensions
while (!str.empty()) {
if (str[0] != '_')
return {};
str = str.substr(1);

std::optional<Extn> extn = read_extn_string(str);
if (!extn)
return {};
vec.push_back(*extn);
}
return vec;
}

static std::vector<Extn> merge_extensions(std::span<Extn> x, std::span<Extn> y) {
std::vector<Extn> vec;

// The base part (i.e. "rv64i" or "rv32i") must match.
if (x[0].name != y[0].name)
return {};

// Merge ISA extension strings
while (!x.empty() && !y.empty()) {
if (x[0].name == y[0].name) {
vec.push_back(extn_version_less(x[0], y[0]) ? y[0] : x[0]);
x = x.subspan(1);
y = y.subspan(1);
} else if (extn_name_less(x[0], y[0])) {
vec.push_back(x[0]);
x = x.subspan(1);
} else {
vec.push_back(y[0]);
y = y.subspan(1);
}
}

vec.insert(vec.end(), x.begin(), x.end());
vec.insert(vec.end(), y.begin(), y.end());
return vec;
}

static std::string to_string(std::span<Extn> v) {
std::string str = std::string(v[0].name) + std::to_string(v[0].major) +
"p" + std::to_string(v[0].minor);

for (i64 i = 1; i < v.size(); i++)
str += "_" + std::string(v[i].name) + std::to_string(v[i].major) +
"p" + std::to_string(v[i].minor);
return str;
}

//
// Output .riscv.attributes class
//

template <typename E> requires is_riscv<E>
void RiscvAttributesSection<E>::update_shdr(Context<E> &ctx) {
if (!contents.empty())
return;

i64 stack = -1;
std::vector<Extn> arch;
bool unaligned = false;

for (ObjectFile<E> *file : ctx.objs) {
if (file->extra.stack_align) {
i64 val = *file->extra.stack_align;
if (stack != -1 && stack != val)
Error(ctx) << *file << ": stack alignment requirement mistmatch";
stack = val;
}

if (file->extra.arch) {
std::vector<Extn> arch2 = parse_arch_string(*file->extra.arch);
if (arch2.empty())
Error(ctx) << *file << ": corrupted .riscv.attributes ISA string: "
<< *file->extra.arch;

if (arch.empty()) {
arch = arch2;
} else {
arch = merge_extensions(arch, arch2);
if (arch.empty())
Error(ctx) << *file << ": incompatible .riscv.attributes ISA string: "
<< *file->extra.arch;
}
}

if (file->extra.unaligned_access.value_or(false))
unaligned = true;
}

if (arch.empty())
return;

std::string arch_str = to_string(arch);
contents.resize(arch_str.size() + 100);

u8 *p = (u8 *)contents.data();
*p++ = 'A'; // Format version
U32<E> *sub_sz = (U32<E> *)p; // Sub-section length
p += 4;
p += write_string(p, "riscv"); // Vendor name
u8 *sub_sub_start = p;
*p++ = ELF_TAG_FILE; // Sub-section tag
U32<E> *sub_sub_sz = (U32<E> *)p; // Sub-sub-section length
p += 4;

if (stack != -1) {
p += write_uleb(p, ELF_TAG_RISCV_STACK_ALIGN);
p += write_uleb(p, stack);
}

p += write_uleb(p, ELF_TAG_RISCV_ARCH);
p += write_string(p, arch_str);

if (unaligned) {
p += write_uleb(p, ELF_TAG_RISCV_UNALIGNED_ACCESS);
p += write_uleb(p, 1);
}

i64 sz = p - (u8 *)contents.data();
*sub_sz = sz - 1;
*sub_sub_sz = p - sub_sub_start;
contents.resize(sz);
this->shdr.sh_size = sz;
}

template <typename E> requires is_riscv<E>
void RiscvAttributesSection<E>::copy_buf(Context<E> &ctx) {
memcpy(ctx.buf + this->shdr.sh_offset, contents.data(), contents.size());
}

#define INSTANTIATE(E) \
template void write_plt_header(Context<E> &, u8 *); \
template void write_plt_entry(Context<E> &, u8 *, Symbol<E> &); \
Expand All @@ -939,7 +1183,8 @@ i64 riscv_resize_sections(Context<E> &ctx) {
template void InputSection<E>::apply_reloc_nonalloc(Context<E> &, u8 *); \
template void InputSection<E>::copy_contents_riscv(Context<E> &, u8 *); \
template void InputSection<E>::scan_relocations(Context<E> &); \
template i64 riscv_resize_sections(Context<E> &);
template i64 riscv_resize_sections(Context<E> &); \
template class RiscvAttributesSection<E>;

INSTANTIATE(RV64LE);
INSTANTIATE(RV64BE);
Expand Down
11 changes: 11 additions & 0 deletions elf/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ enum : u32 {
SHT_X86_64_UNWIND = 0x70000001,
SHT_ARM_EXIDX = 0x70000001,
SHT_ARM_ATTRIBUTES = 0x70000003,
SHT_RISCV_ATTRIBUTES = 0x70000003,
};

enum : u32 {
Expand Down Expand Up @@ -186,6 +187,7 @@ enum : u32 {
PT_GNU_RELRO = 0x6474e552,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
};

enum : u32 {
Expand Down Expand Up @@ -381,6 +383,15 @@ enum : u32 {
STO_ALPHA_STD_GPLOAD = 0x22,
};

enum : u32 {
ELF_TAG_FILE = 1,
ELF_TAG_SECTION = 2,
ELF_TAG_SYMBOL = 3,
ELF_TAG_RISCV_STACK_ALIGN = 4,
ELF_TAG_RISCV_ARCH = 5,
ELF_TAG_RISCV_UNALIGNED_ACCESS = 6,
};

//
// Relocation types
//
Expand Down
68 changes: 67 additions & 1 deletion elf/input-files.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,62 @@ ObjectFile<E>::read_note_gnu_property(Context<E> &ctx, const ElfShdr<E> &shdr) {
}
}

static inline std::string_view read_string(std::string_view &str) {
i64 pos = str.find_first_of('\0');
std::string_view val = str.substr(0, pos);
str = str.substr(pos + 1);
return val;
}

// <format-version>
// [ <section-length> "vendor-name" <file-tag> <size> <attribute>*]+ ]*
template <typename E>
static void read_riscv_attributes(Context<E> &ctx, ObjectFile<E> &file,
std::string_view data) {
const char *begin = data.data();
if (data.empty())
Fatal(ctx) << file << ": corrupted .riscv.attributes section";

if (u8 format_version = data[0]; format_version != 'A')
return;
data = data.substr(1);

while (!data.empty()) {
i64 sz = *(U32<E> *)data.data();
if (data.size() < sz)
Fatal(ctx) << file << ": corrupted .riscv.attributes section";

std::string_view p(data.data() + 4, sz - 4);
data = data.substr(sz);

if (!p.starts_with("riscv\0"sv))
continue;
p = p.substr(6);

if (!p.starts_with(ELF_TAG_FILE))
Fatal(ctx) << file << ": corrupted .riscv.attributes section";
p = p.substr(5); // skip the tag and the sub-sub-section size

while (!p.empty()) {
i64 tag = read_uleb(p);

switch (tag) {
case ELF_TAG_RISCV_STACK_ALIGN:
file.extra.stack_align = read_uleb(p);
break;
case ELF_TAG_RISCV_ARCH:
file.extra.arch = read_string(p);
break;
case ELF_TAG_RISCV_UNALIGNED_ACCESS:
file.extra.unaligned_access = read_uleb(p);
break;
default:
break;
}
}
}
}

template <typename E>
static u64 read_mips_gp0(Context<E> &ctx, InputSection<E> &isec) {
std::string_view data = isec.contents;
Expand Down Expand Up @@ -170,6 +226,17 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
shdr.sh_type != SHT_LLVM_ADDRSIG && !ctx.arg.relocatable)
continue;

if constexpr (is_arm<E>)
if (shdr.sh_type == SHT_ARM_ATTRIBUTES)
continue;

if constexpr (is_riscv<E>) {
if (shdr.sh_type == SHT_RISCV_ATTRIBUTES) {
read_riscv_attributes(ctx, *this, this->get_string(ctx, shdr));
continue;
}
}

switch (shdr.sh_type) {
case SHT_GROUP: {
// Get the signature of this section group.
Expand Down Expand Up @@ -212,7 +279,6 @@ void ObjectFile<E>::initialize_sections(Context<E> &ctx) {
case SHT_REL:
case SHT_RELA:
case SHT_NULL:
case SHT_ARM_ATTRIBUTES:
break;
default: {
std::string_view name = this->shstrtab.data() + shdr.sh_name;
Expand Down
Loading

0 comments on commit aa64491

Please sign in to comment.