#![deny(clippy::arithmetic_side_effects)]
use crate::arch::s390x::{self as insn, Field};
pub use super::{
ApplyError, Class, ClassifyContext, ClassifyError, GotKind, Kind, RelaxValues, TlsMode, Width,
};
pub const R_390_NONE: u32 = 0;
pub const R_390_8: u32 = 1;
pub const R_390_12: u32 = 2;
pub const R_390_16: u32 = 3;
pub const R_390_32: u32 = 4;
pub const R_390_PC32: u32 = 5;
pub const R_390_GOT12: u32 = 6;
pub const R_390_GOT32: u32 = 7;
pub const R_390_PLT32: u32 = 8;
pub const R_390_COPY: u32 = 9;
pub const R_390_GLOB_DAT: u32 = 10;
pub const R_390_JMP_SLOT: u32 = 11;
pub const R_390_RELATIVE: u32 = 12;
pub const R_390_GOTOFF32: u32 = 13;
pub const R_390_GOTPC: u32 = 14;
pub const R_390_GOT16: u32 = 15;
pub const R_390_PC16: u32 = 16;
pub const R_390_PC16DBL: u32 = 17;
pub const R_390_PLT16DBL: u32 = 18;
pub const R_390_PC32DBL: u32 = 19;
pub const R_390_PLT32DBL: u32 = 20;
pub const R_390_GOTPCDBL: u32 = 21;
pub const R_390_64: u32 = 22;
pub const R_390_PC64: u32 = 23;
pub const R_390_GOT64: u32 = 24;
pub const R_390_PLT64: u32 = 25;
pub const R_390_GOTENT: u32 = 26;
pub const R_390_GOTOFF16: u32 = 27;
pub const R_390_GOTOFF64: u32 = 28;
pub const R_390_GOTPLT12: u32 = 29;
pub const R_390_GOTPLT16: u32 = 30;
pub const R_390_GOTPLT32: u32 = 31;
pub const R_390_GOTPLT64: u32 = 32;
pub const R_390_GOTPLTENT: u32 = 33;
pub const R_390_PLTOFF16: u32 = 34;
pub const R_390_PLTOFF32: u32 = 35;
pub const R_390_PLTOFF64: u32 = 36;
pub const R_390_TLS_LOAD: u32 = 37;
pub const R_390_TLS_GDCALL: u32 = 38;
pub const R_390_TLS_LDCALL: u32 = 39;
pub const R_390_TLS_GD32: u32 = 40;
pub const R_390_TLS_GD64: u32 = 41;
pub const R_390_TLS_GOTIE12: u32 = 42;
pub const R_390_TLS_GOTIE32: u32 = 43;
pub const R_390_TLS_GOTIE64: u32 = 44;
pub const R_390_TLS_LDM32: u32 = 45;
pub const R_390_TLS_LDM64: u32 = 46;
pub const R_390_TLS_IE32: u32 = 47;
pub const R_390_TLS_IE64: u32 = 48;
pub const R_390_TLS_IEENT: u32 = 49;
pub const R_390_TLS_LE32: u32 = 50;
pub const R_390_TLS_LE64: u32 = 51;
pub const R_390_TLS_LDO32: u32 = 52;
pub const R_390_TLS_LDO64: u32 = 53;
pub const R_390_TLS_DTPMOD: u32 = 54;
pub const R_390_TLS_DTPOFF: u32 = 55;
pub const R_390_TLS_TPOFF: u32 = 56;
pub const R_390_20: u32 = 57;
pub const R_390_GOT20: u32 = 58;
pub const R_390_GOTPLT20: u32 = 59;
pub const R_390_TLS_GOTIE20: u32 = 60;
pub const R_390_IRELATIVE: u32 = 61;
pub const R_390_PC12DBL: u32 = 62;
pub const R_390_PLT12DBL: u32 = 63;
pub const R_390_PC24DBL: u32 = 64;
pub const R_390_PLT24DBL: u32 = 65;
pub const R_390_GNU_VTINHERIT: u32 = 250;
pub const R_390_GNU_VTENTRY: u32 = 251;
pub const PT_S390_PGSTE: u32 = 0x7000_0000;
#[must_use]
pub fn reloc_name(r_type: u32) -> Option<&'static str> {
const NAMES: [&str; 66] = [
"R_390_NONE",
"R_390_8",
"R_390_12",
"R_390_16",
"R_390_32",
"R_390_PC32",
"R_390_GOT12",
"R_390_GOT32",
"R_390_PLT32",
"R_390_COPY",
"R_390_GLOB_DAT",
"R_390_JMP_SLOT",
"R_390_RELATIVE",
"R_390_GOTOFF32",
"R_390_GOTPC",
"R_390_GOT16",
"R_390_PC16",
"R_390_PC16DBL",
"R_390_PLT16DBL",
"R_390_PC32DBL",
"R_390_PLT32DBL",
"R_390_GOTPCDBL",
"R_390_64",
"R_390_PC64",
"R_390_GOT64",
"R_390_PLT64",
"R_390_GOTENT",
"R_390_GOTOFF16",
"R_390_GOTOFF64",
"R_390_GOTPLT12",
"R_390_GOTPLT16",
"R_390_GOTPLT32",
"R_390_GOTPLT64",
"R_390_GOTPLTENT",
"R_390_PLTOFF16",
"R_390_PLTOFF32",
"R_390_PLTOFF64",
"R_390_TLS_LOAD",
"R_390_TLS_GDCALL",
"R_390_TLS_LDCALL",
"R_390_TLS_GD32",
"R_390_TLS_GD64",
"R_390_TLS_GOTIE12",
"R_390_TLS_GOTIE32",
"R_390_TLS_GOTIE64",
"R_390_TLS_LDM32",
"R_390_TLS_LDM64",
"R_390_TLS_IE32",
"R_390_TLS_IE64",
"R_390_TLS_IEENT",
"R_390_TLS_LE32",
"R_390_TLS_LE64",
"R_390_TLS_LDO32",
"R_390_TLS_LDO64",
"R_390_TLS_DTPMOD",
"R_390_TLS_DTPOFF",
"R_390_TLS_TPOFF",
"R_390_20",
"R_390_GOT20",
"R_390_GOTPLT20",
"R_390_TLS_GOTIE20",
"R_390_IRELATIVE",
"R_390_PC12DBL",
"R_390_PLT12DBL",
"R_390_PC24DBL",
"R_390_PLT24DBL",
];
match r_type {
R_390_GNU_VTINHERIT => Some("R_390_GNU_VTINHERIT"),
R_390_GNU_VTENTRY => Some("R_390_GNU_VTENTRY"),
_ => NAMES.get(usize::try_from(r_type).ok()?).copied(),
}
}
const fn class(kind: Kind, width: Width) -> Class {
Class::new(kind, width)
}
const fn got(kind: Kind, width: Width, slot: GotKind) -> Class {
Class::new(kind, width).through(slot)
}
const fn field(field: Field) -> Width {
Width::S390(field)
}
#[must_use]
pub fn is_branch(r_type: u32) -> bool {
matches!(
r_type,
R_390_PLT12DBL
| R_390_PLT16DBL
| R_390_PLT24DBL
| R_390_PLT32DBL
| R_390_PLT32
| R_390_PLT64
)
}
fn bytes_at(data: &[u8], offset: u64, back: u64, len: usize) -> Option<&[u8]> {
let start = usize::try_from(offset.checked_sub(back)?).ok()?;
data.get(start..start.checked_add(len)?)
}
fn got_load_relaxes(r_type: u32, data: &[u8], offset: u64, context: ClassifyContext) -> bool {
if !(context.relax_got && context.pic) {
return false;
}
match r_type {
R_390_GOTENT => bytes_at(data, offset, 2, 2)
.and_then(<[u8]>::first_chunk::<2>)
.is_some_and(|op| insn::is_lgrl(*op)),
R_390_GOT20 => bytes_at(data, offset, 2, 6).is_some_and(insn::is_lg_got),
_ => false,
}
}
pub fn classify(
r_type: u32,
data: &[u8],
offset: u64,
context: ClassifyContext,
) -> Result<Class, ClassifyError> {
use Kind as K;
use Width as W;
let brasl = || {
if bytes_at(data, offset, 0, 6).is_some_and(insn::is_brasl_r14) {
Ok(())
} else {
Err(ClassifyError::BadTlsInstruction)
}
};
Ok(match r_type {
R_390_NONE | R_390_GNU_VTINHERIT | R_390_GNU_VTENTRY => class(K::None, W::None),
R_390_8 => class(K::Abs, W::Any8),
R_390_12 => class(K::Abs, field(Field::Imm12)),
R_390_16 => class(K::Abs, W::Any16),
R_390_20 => class(K::Abs, field(Field::Disp20)),
R_390_32 => class(K::Abs, W::Any32),
R_390_64 => class(K::Abs, W::W64),
R_390_PC16 => class(K::Pc, W::Any16),
R_390_PC32 | R_390_PLT32 => class(K::Pc, W::Any32),
R_390_PC64 | R_390_PLT64 => class(K::Pc, W::W64),
R_390_PC12DBL | R_390_PLT12DBL => class(K::Pc, field(Field::Pc12Dbl)),
R_390_PC16DBL | R_390_PLT16DBL => class(K::Pc, field(Field::Pc16Dbl)),
R_390_PC24DBL | R_390_PLT24DBL => class(K::Pc, field(Field::Pc24Dbl)),
R_390_PC32DBL | R_390_PLT32DBL => class(K::Pc, field(Field::Pc32Dbl)),
R_390_GOT12 | R_390_GOTPLT12 => class(K::GotSlotRel, field(Field::Imm12)),
R_390_GOT16 | R_390_GOTPLT16 => class(K::GotSlotRel, W::Any16),
R_390_GOT20 if got_load_relaxes(r_type, data, offset, context) => {
class(K::GotRelax, field(Field::Disp20))
}
R_390_GOT20 | R_390_GOTPLT20 => class(K::GotSlotRel, field(Field::Disp20)),
R_390_GOT32 | R_390_GOTPLT32 => class(K::GotSlotRel, W::Any32),
R_390_GOT64 | R_390_GOTPLT64 => class(K::GotSlotRel, W::W64),
R_390_GOTENT if got_load_relaxes(r_type, data, offset, context) => {
class(K::GotRelax, field(Field::Pc32Dbl))
}
R_390_GOTENT | R_390_GOTPLTENT => class(K::Got, field(Field::Pc32Dbl)),
R_390_GOTOFF16 => class(K::GotRel, W::Any16),
R_390_GOTOFF32 => class(K::GotRel, W::Any32),
R_390_GOTOFF64 => class(K::GotRel, W::W64),
R_390_GOTPC => class(K::GotBasePc, W::W64),
R_390_GOTPCDBL => class(K::GotBasePc, field(Field::Pc32Dbl)),
R_390_TLS_GD64 => match context.tls {
TlsMode::Dynamic => got(K::GotSlotRel, W::W64, GotKind::TlsGd),
TlsMode::InitialExec => class(K::GdToIe, W::W64),
TlsMode::LocalExec => class(K::GdToLe, W::W64),
},
R_390_TLS_LDM64 => match context.tls_ld {
TlsMode::LocalExec => class(K::LdToLe, W::W64),
_ => got(K::GotSlotRel, W::W64, GotKind::TlsLd),
},
R_390_TLS_LDO64 => class(K::DtpOff, W::W64),
R_390_TLS_GOTIE64 => match context.tls {
TlsMode::LocalExec => class(K::IeToLe, W::W64),
_ => got(K::GotSlotRel, W::W64, GotKind::TpOff),
},
R_390_TLS_IE64 => match context.tls {
TlsMode::LocalExec => class(K::IeToLe, W::W64),
_ => got(K::GotAbs, W::W64, GotKind::TpOff),
},
R_390_TLS_LE64 => class(K::TpOff, W::W64),
R_390_TLS_GOTIE12 => got(K::GotSlotRel, field(Field::Imm12), GotKind::TpOff),
R_390_TLS_GOTIE20 => got(K::GotSlotRel, field(Field::Disp20), GotKind::TpOff),
R_390_TLS_IEENT => got(K::Got, field(Field::Pc32Dbl), GotKind::TpOff),
R_390_TLS_LOAD => match context.tls {
TlsMode::LocalExec => class(K::IeToLe, W::None),
_ => class(K::None, W::None),
},
R_390_TLS_GDCALL => match context.tls {
TlsMode::Dynamic => class(K::None, W::None),
TlsMode::InitialExec => {
brasl()?;
class(K::GdToIe, W::None)
}
TlsMode::LocalExec => {
brasl()?;
class(K::GdToLe, W::None)
}
},
R_390_TLS_LDCALL => match context.tls_ld {
TlsMode::LocalExec => {
brasl()?;
class(K::LdToLe, W::None)
}
_ => class(K::None, W::None),
},
_ => return Err(ClassifyError::Unsupported),
})
}
fn slot<const N: usize>(out: &mut [u8], at: u64) -> Result<&mut [u8; N], ApplyError> {
let start = usize::try_from(at).map_err(|_| ApplyError::OutOfBounds)?;
out.get_mut(start..)
.and_then(|rest| rest.first_chunk_mut::<N>())
.ok_or(ApplyError::OutOfBounds)
}
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 end = start
.checked_add(field.bytes())
.ok_or(ApplyError::OutOfBounds)?;
let bytes = out.get_mut(start..end).ok_or(ApplyError::OutOfBounds)?;
insn::encode(field, bytes, value as i64).map_err(|_| ApplyError::Overflow)
}
pub fn relax_got(
out: &mut [u8],
offset: u64,
r_type: u32,
symbol: u64,
values: RelaxValues,
) -> Result<(), ApplyError> {
let entry = values.got;
if symbol & 1 != 0 {
return if r_type == R_390_GOT20 {
write_field(
out,
offset,
Field::Disp20,
entry.wrapping_sub(values.got_base),
)
} else {
write_field(
out,
offset,
Field::Pc32Dbl,
entry.wrapping_sub(values.place),
)
};
}
let start = offset.checked_sub(2).ok_or(ApplyError::OutOfBounds)?;
let opcode = slot::<2>(out, start)?;
*opcode = insn::larl(opcode[1]);
write_field(
out,
offset,
Field::Pc32Dbl,
symbol.wrapping_add(2).wrapping_sub(values.place),
)
}
pub fn relax_tls(
out: &mut [u8],
offset: u64,
kind: Kind,
r_type: u32,
values: RelaxValues,
) -> Result<(), ApplyError> {
let word = |out: &mut [u8], value: u64| -> Result<(), ApplyError> {
*slot::<8>(out, offset)? = value.to_be_bytes();
Ok(())
};
let call = |out: &mut [u8], code: [u8; 6]| -> Result<(), ApplyError> {
let insn = slot::<6>(out, offset)?;
if !insn::is_brasl_r14(insn) {
return Err(ApplyError::BadInstruction);
}
*insn = code;
Ok(())
};
match (kind, r_type) {
(Kind::GdToIe, R_390_TLS_GD64) => word(out, values.got.wrapping_sub(values.got_base)),
(Kind::GdToLe | Kind::IeToLe, R_390_TLS_GD64 | R_390_TLS_GOTIE64 | R_390_TLS_IE64) => {
word(out, values.tpoff as u64)
}
(Kind::LdToLe, R_390_TLS_LDM64) => word(out, 0),
(Kind::GdToIe, R_390_TLS_GDCALL) => call(out, insn::LG_R2_GOT),
(Kind::GdToLe, R_390_TLS_GDCALL) | (Kind::LdToLe, R_390_TLS_LDCALL) => {
call(out, insn::BRCL_NOP)
}
(Kind::IeToLe, R_390_TLS_LOAD) => {
let load = slot::<6>(out, offset)?;
*load = insn::ie_load_to_le(*load).ok_or(ApplyError::BadInstruction)?;
Ok(())
}
_ => Err(ApplyError::BadInstruction),
}
}
pub const PLT_HEADER_SIZE: u64 = insn::PLT_HEADER_SIZE;
pub const PLT_ENTRY_SIZE: u64 = insn::PLT_ENTRY_SIZE;
pub fn write_plt_header(out: &mut [u8], plt: u64, got: u64) -> Result<(), ApplyError> {
let code = insn::plt_header(plt, got).map_err(|_| ApplyError::Overflow)?;
*slot::<32>(out, 0)? = code;
Ok(())
}
pub fn write_plt_entry(
out: &mut [u8],
entry: u64,
slot_address: u64,
index: u32,
plt: u64,
) -> Result<(), ApplyError> {
let rela = index.checked_mul(24).ok_or(ApplyError::Overflow)?;
let code = insn::plt_entry(entry, slot_address, plt, rela).map_err(|_| ApplyError::Overflow)?;
*slot::<32>(out, 0)? = code;
Ok(())
}
pub fn write_iplt(out: &mut [u8], stub: u64, slot_address: u64) -> Result<(), ApplyError> {
let code = insn::plt_entry(stub, slot_address, stub.wrapping_add(22), 0)
.map_err(|_| ApplyError::Overflow)?;
*slot::<32>(out, 0)? = code;
Ok(())
}
pub fn write_nops(out: &mut [u8]) {
insn::write_nops(out);
}
#[cfg(test)]
mod tests {
use super::*;
fn context(tls: TlsMode, pic: bool) -> ClassifyContext {
ClassifyContext {
relax_got: true,
pic,
tls,
tls_ld: tls,
code: true,
}
}
#[test]
fn names() {
assert_eq!(reloc_name(R_390_PC32DBL), Some("R_390_PC32DBL"));
assert_eq!(reloc_name(R_390_PLT24DBL), Some("R_390_PLT24DBL"));
assert_eq!(reloc_name(R_390_GNU_VTENTRY), Some("R_390_GNU_VTENTRY"));
assert_eq!(reloc_name(66), None);
}
fn values(got: u64, place: u64, got_base: u64) -> RelaxValues {
RelaxValues {
got,
place,
got_base,
..RelaxValues::default()
}
}
#[test]
fn got_loads_relax_only_in_pic() {
let data = [0xc4, 0x28, 0, 0, 0, 0];
let pie = context(TlsMode::LocalExec, true);
let exe = context(TlsMode::LocalExec, false);
assert_eq!(
classify(R_390_GOTENT, &data, 2, pie).unwrap().kind,
Kind::GotRelax
);
assert_eq!(
classify(R_390_GOTENT, &data, 2, exe).unwrap().kind,
Kind::Got
);
let mut out = data;
relax_got(&mut out, 2, R_390_GOTENT, 0x1000, values(0x842, 0x802, 0)).unwrap();
assert_eq!(out, [0xc0, 0x20, 0, 0, 0x04, 0x00]);
let mut out = data;
relax_got(&mut out, 2, R_390_GOTENT, 0x1001, values(0x842, 0x802, 0)).unwrap();
assert_eq!(out, [0xc4, 0x28, 0, 0, 0, 0x20]);
let lg = [0xe3, 0x10, 0xc0, 0x00, 0x00, 0x04];
assert_eq!(
classify(R_390_GOT20, &lg, 2, pie).unwrap().kind,
Kind::GotRelax
);
let mut out = lg;
relax_got(
&mut out,
2,
R_390_GOT20,
0x1000,
values(0x2040, 0x802, 0x2000),
)
.unwrap();
assert_eq!(out, [0xc0, 0x10, 0, 0, 0x04, 0x00]);
}
#[test]
fn tls_markers() {
let brasl = [0xc0, 0xe5, 0, 0, 0, 0];
let le = context(TlsMode::LocalExec, false);
let ie = context(TlsMode::InitialExec, true);
let dynamic = context(TlsMode::Dynamic, true);
assert_eq!(
classify(R_390_TLS_GDCALL, &brasl, 0, le).unwrap().kind,
Kind::GdToLe
);
assert_eq!(
classify(R_390_TLS_GDCALL, &brasl, 0, ie).unwrap().kind,
Kind::GdToIe
);
assert_eq!(
classify(R_390_TLS_GDCALL, &brasl, 0, dynamic).unwrap().kind,
Kind::None
);
assert_eq!(
classify(R_390_TLS_GDCALL, &[0; 6], 0, le),
Err(ClassifyError::BadTlsInstruction)
);
let mut out = brasl;
relax_tls(
&mut out,
0,
Kind::GdToIe,
R_390_TLS_GDCALL,
RelaxValues::default(),
)
.unwrap();
assert_eq!(out, insn::LG_R2_GOT);
let mut pool = [0u8; 8];
let values = RelaxValues {
tpoff: -16,
..RelaxValues::default()
};
relax_tls(&mut pool, 0, Kind::GdToLe, R_390_TLS_GD64, values).unwrap();
assert_eq!(pool, (-16i64).to_be_bytes());
let values = RelaxValues {
got: 0x2018,
got_base: 0x2000,
..RelaxValues::default()
};
relax_tls(&mut pool, 0, Kind::GdToIe, R_390_TLS_GD64, values).unwrap();
assert_eq!(pool, 0x18u64.to_be_bytes());
assert_eq!(
classify(R_390_TLS_GD32, &pool, 0, le),
Err(ClassifyError::Unsupported)
);
}
}