#![deny(clippy::arithmetic_side_effects)]
pub mod apply;
pub mod attributes;
pub mod relax;
use crate::arch::riscv::{
self as insn, AUIPC, Field, JALR, LD, LW, NOP, SRLI, SUB, T0, T1, T2, T3, hi20, itype, lo12,
rtype, utype,
};
use crate::elf::read::consts::riscv::*;
use super::{ApplyError, Class, ClassifyContext, ClassifyError, GotKind, Kind, TlsMode, Width};
pub const EF_RISCV_RVC: u32 = 0x1;
pub const EF_RISCV_FLOAT_ABI: u32 = 0x6;
pub const EF_RISCV_RVE: u32 = 0x8;
pub const PT_RISCV_ATTRIBUTES: u32 = 0x7000_0003;
const fn class(kind: Kind, field: Field) -> Class {
Class::new(kind, Width::RiscV(field))
}
const fn got(kind: Kind, field: Field, slot: GotKind) -> Class {
class(kind, field).through(slot)
}
const fn none() -> Class {
Class::new(Kind::None, Width::None)
}
#[must_use]
pub const fn is_pcrel_hi(r_type: u32) -> bool {
matches!(
r_type,
R_RISCV_PCREL_HI20 | R_RISCV_GOT_HI20 | R_RISCV_TLS_GD_HI20 | R_RISCV_TLS_GOT_HI20
)
}
#[inline(never)]
pub fn classify(r_type: u32, context: ClassifyContext) -> Result<Class, ClassifyError> {
use Field as F;
use Kind as K;
Ok(match r_type {
R_RISCV_NONE | R_RISCV_RELAX | R_RISCV_ALIGN | R_RISCV_TPREL_ADD | R_RISCV_VENDOR => none(),
R_RISCV_PCREL_LO12_I
| R_RISCV_PCREL_LO12_S
| R_RISCV_TLSDESC_LOAD_LO12
| R_RISCV_TLSDESC_ADD_LO12
| R_RISCV_TLSDESC_CALL => none(),
R_RISCV_32 => class(K::Abs, F::Word32),
R_RISCV_64 => Class::new(K::Abs, Width::W64),
R_RISCV_HI20 => class(K::Abs, F::Hi20),
R_RISCV_LO12_I => class(K::Abs, F::Lo12I),
R_RISCV_LO12_S => class(K::Abs, F::Lo12S),
R_RISCV_BRANCH => class(K::Pc, F::Branch),
R_RISCV_JAL => class(K::Pc, F::Jal),
R_RISCV_RVC_BRANCH => class(K::Pc, F::RvcBranch),
R_RISCV_RVC_JUMP => class(K::Pc, F::RvcJump),
R_RISCV_CALL | R_RISCV_CALL_PLT => class(K::Pc, F::Call),
R_RISCV_PLT32 | R_RISCV_32_PCREL => class(K::Pc, F::Word32Signed),
R_RISCV_PCREL_HI20 => class(K::Pc, F::Hi20),
R_RISCV_GOT_HI20 => got(K::Got, F::Hi20, GotKind::Address),
R_RISCV_GOT32_PCREL => got(K::Got, F::Word32Signed, GotKind::Address),
R_RISCV_TPREL_HI20 => class(K::TpOff, F::Hi20),
R_RISCV_TPREL_LO12_I => class(K::TpOff, F::Lo12I),
R_RISCV_TPREL_LO12_S => class(K::TpOff, F::Lo12S),
R_RISCV_TLS_GOT_HI20 => got(K::Got, F::Hi20, GotKind::TpOff),
R_RISCV_TLS_GD_HI20 => got(K::Got, F::Hi20, GotKind::TlsGd),
R_RISCV_TLSDESC_HI20 => match context.tls {
TlsMode::Dynamic => got(K::Got, F::Hi20, GotKind::TlsDesc),
TlsMode::LocalExec => Class::new(K::DescToLe, Width::None),
TlsMode::InitialExec => Class::new(K::DescToIe, Width::None),
},
R_RISCV_TLS_DTPREL32 => class(K::DtpOff, F::Dtprel32),
R_RISCV_TLS_DTPREL64 => class(K::DtpOff, F::Dtprel64),
R_RISCV_ADD8 => Class::new(K::Add, Width::Any8),
R_RISCV_ADD16 => Class::new(K::Add, Width::Any16),
R_RISCV_ADD32 => Class::new(K::Add, Width::U32),
R_RISCV_ADD64 => Class::new(K::Add, Width::W64),
R_RISCV_SUB6 => class(K::Abs, F::Sub6),
R_RISCV_SUB8 => Class::new(K::Sub, Width::Any8),
R_RISCV_SUB16 => Class::new(K::Sub, Width::Any16),
R_RISCV_SUB32 => Class::new(K::Sub, Width::U32),
R_RISCV_SUB64 => Class::new(K::Sub, Width::W64),
R_RISCV_SET6 => class(K::Abs, F::Set6),
R_RISCV_SET8 => class(K::Abs, F::Set8),
R_RISCV_SET16 => class(K::Abs, F::Set16),
R_RISCV_SET32 => class(K::Abs, F::Set32),
R_RISCV_SET_ULEB128 => class(K::Abs, F::SetUleb128),
R_RISCV_SUB_ULEB128 => class(K::Abs, F::SubUleb128),
_ => return Err(ClassifyError::Unsupported),
})
}
#[must_use]
pub const fn is_branch(r_type: u32) -> bool {
matches!(r_type, R_RISCV_CALL | R_RISCV_CALL_PLT | R_RISCV_PLT32)
}
fn put(out: &mut [u8], at: u64, value: u32) -> Result<(), ApplyError> {
let at = usize::try_from(at).map_err(|_| ApplyError::OutOfBounds)?;
insn::write32(out, at, value).ok_or(ApplyError::OutOfBounds)
}
#[inline(never)]
pub fn write_field(
out: &mut [u8],
offset: u64,
field: Field,
value: u64,
) -> Result<(), ApplyError> {
let start = usize::try_from(offset).map_err(|_| ApplyError::OutOfBounds)?;
let data = out.get_mut(start..).ok_or(ApplyError::OutOfBounds)?;
field.apply(data, value).map_err(|error| match error {
insn::FieldError::Overflow => ApplyError::Overflow,
insn::FieldError::OutOfBounds => ApplyError::OutOfBounds,
})
}
#[must_use]
pub const fn load(word: u64) -> u32 {
if word == 4 { LW } else { LD }
}
fn pc_offset(from: u64, to: u64, word: u64) -> u64 {
let offset = to.wrapping_sub(from);
if word == 4 {
insn::wrap32_hi(offset)
} else {
offset
}
}
pub const PLT_HEADER_SIZE: u64 = 32;
pub const PLT_ENTRY_SIZE: u64 = 16;
pub fn write_plt_header(
out: &mut [u8],
plt: u64,
got_plt: u64,
word: u64,
) -> Result<(), ApplyError> {
let offset = pc_offset(plt, got_plt, word);
insn::check_hi(offset).map_err(|_| ApplyError::Overflow)?;
let header_adjust = 0u32.wrapping_sub(PLT_HEADER_SIZE as u32).wrapping_sub(12);
let shift = if word == 4 { 2 } else { 1 };
let words = [
utype(AUIPC, T2, hi20(offset)),
rtype(SUB, T1, T1, T3),
itype(load(word), T3, T2, lo12(offset)),
itype(insn::ADDI, T1, T1, header_adjust),
itype(insn::ADDI, T0, T2, lo12(offset)),
itype(SRLI, T1, T1, shift),
itype(load(word), T0, T0, word as u32),
itype(JALR, 0, T3, 0),
];
for (index, word) in (0u64..).zip(words) {
put(out, index.wrapping_mul(4), word)?;
}
Ok(())
}
pub fn write_plt_entry(out: &mut [u8], entry: u64, slot: u64, word: u64) -> Result<(), ApplyError> {
let offset = pc_offset(entry, slot, word);
insn::check_hi(offset).map_err(|_| ApplyError::Overflow)?;
put(out, 0, utype(AUIPC, T3, hi20(offset)))?;
put(out, 4, itype(load(word), T3, T3, lo12(offset)))?;
put(out, 8, itype(JALR, T1, T3, 0))?;
put(out, 12, NOP)
}
pub fn write_nops(out: &mut [u8]) {
out.fill(0);
}
#[must_use]
pub fn output_flags(flags: impl IntoIterator<Item = u32>) -> u32 {
let mut flags = flags.into_iter();
let Some(first) = flags.next() else {
return 0;
};
flags.fold(first, |merged, f| merged | (f & EF_RISCV_RVC))
}
#[must_use]
pub fn incompatible_flags(first: u32, flags: u32) -> Option<&'static str> {
if flags & EF_RISCV_FLOAT_ABI != first & EF_RISCV_FLOAT_ABI {
return Some("floating-point ABI");
}
if flags & EF_RISCV_RVE != first & EF_RISCV_RVE {
return Some("EF_RISCV_RVE");
}
None
}
#[must_use]
pub fn interpreter(flags: u32, word: u64) -> &'static str {
match (word, flags & EF_RISCV_FLOAT_ABI) {
(4, 0) => "/lib/ld-linux-riscv32-ilp32.so.1",
(4, 2) => "/lib/ld-linux-riscv32-ilp32f.so.1",
(4, _) => "/lib/ld-linux-riscv32-ilp32d.so.1",
(_, 0) => "/lib/ld-linux-riscv64-lp64.so.1",
(_, 2) => "/lib/ld-linux-riscv64-lp64f.so.1",
_ => "/lib/ld-linux-riscv64-lp64d.so.1",
}
}
#[cfg(test)]
mod tests {
use super::*;
fn exec() -> ClassifyContext {
ClassifyContext::static_exec(true)
}
fn words(bytes: &[u8]) -> Vec<u32> {
bytes
.as_chunks::<4>()
.0
.iter()
.map(|w| u32::from_le_bytes(*w))
.collect()
}
#[test]
fn relocations_are_classified() {
assert_eq!(
classify(R_RISCV_64, exec()).unwrap(),
Class::new(Kind::Abs, Width::W64)
);
assert_eq!(
classify(R_RISCV_CALL_PLT, exec()).unwrap().width,
Width::RiscV(Field::Call)
);
let got = classify(R_RISCV_GOT_HI20, exec()).unwrap();
assert!(got.needs_got());
let ie = classify(R_RISCV_TLS_GOT_HI20, exec()).unwrap();
assert!(ie.needs_gottpoff());
assert_eq!(
classify(R_RISCV_PCREL_LO12_I, exec()).unwrap().kind,
Kind::None
);
assert_eq!(
classify(R_RISCV_TLSDESC_HI20, exec()).unwrap().kind,
Kind::DescToLe
);
for r_type in [R_RISCV_RVC_LUI, R_RISCV_GPREL_I, R_RISCV_RELATIVE, 200] {
assert_eq!(
classify(r_type, exec()),
Err(ClassifyError::Unsupported),
"{r_type}"
);
}
}
#[test]
fn the_word_relocation_differs_by_width() {
use super::super::Arch;
let word32 = classify(R_RISCV_32, exec()).unwrap().width;
let word64 = classify(R_RISCV_64, exec()).unwrap().width;
assert!(Arch::RiscV32.is_word(word32));
assert!(!Arch::RiscV32.is_word(word64));
assert!(Arch::RiscV64.is_word(word64));
assert!(!Arch::RiscV64.is_word(word32));
}
#[test]
fn plt_matches_lld() {
let mut header = [0u8; 32];
write_plt_header(&mut header, 0x1310, 0x3400, 8).unwrap();
assert_eq!(
words(&header),
[
0x0000_2397, 0x41c3_0333, 0x0f03_be03, 0xfd43_0313, 0x0f03_8293, 0x0013_5313, 0x0082_b283, 0x000e_0067, ]
);
let mut entry = [0u8; 16];
write_plt_entry(&mut entry, 0x1330, 0x3410, 8).unwrap();
assert_eq!(words(&entry), [0x0000_2e17, 0x0e0e_3e03, 0x000e_0367, NOP]);
}
#[test]
fn rv32_plt_matches_lld() {
let mut header = [0u8; 32];
write_plt_header(&mut header, 0x1480, 0x356c, 4).unwrap();
assert_eq!(
words(&header),
[
0x0000_2397, 0x41c3_0333, 0x0ec3_ae03, 0xfd43_0313, 0x0ec3_8293, 0x0023_5313, 0x0042_a283, 0x000e_0067, ]
);
let mut entry = [0u8; 16];
write_plt_entry(&mut entry, 0x14a0, 0x3574, 4).unwrap();
assert_eq!(words(&entry), [0x0000_2e17, 0x0d4e_2e03, 0x000e_0367, NOP]);
}
#[test]
fn flags_merge_rvc_and_reject_abi_mismatches() {
assert_eq!(output_flags([0x4, 0x5, 0x4]), 0x5);
assert_eq!(output_flags([]), 0);
assert_eq!(incompatible_flags(0x5, 0x1), Some("floating-point ABI"));
assert_eq!(incompatible_flags(0x5, 0x4), None);
assert_eq!(interpreter(0x5, 8), "/lib/ld-linux-riscv64-lp64d.so.1");
assert_eq!(interpreter(0x1, 4), "/lib/ld-linux-riscv32-ilp32.so.1");
assert_eq!(interpreter(0x3, 4), "/lib/ld-linux-riscv32-ilp32f.so.1");
}
}