mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Merge ARM64 feature properties and emit BTI PLTs
We used to merge only x86 properties in .note.gnu.property, so an ARM64 output lost GNU_PROPERTY_AARCH64_FEATURE_1_AND even if all its inputs were compiled with -mbranch-protection. We now AND-merge it as GNU ld and lld do. If the output is BTI-enabled, the PLT header and the PLT entries that may be reached by an indirect branch start with `bti c` as lld's do, which makes PLT entries 24 bytes long, and DT_AARCH64_BTI_PLT is set.
This commit is contained in:
1 parent
4ba7792e43
commit
f03b745952
11 files changed
+214
-43
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! `.dynamic`, information consumed by the dynamic linker.
|
||||
|
||||
use crate::chunks::ChunkHeader;
|
||||
use crate::chunks::{ChunkHeader, note_property};
|
||||
use crate::context::Context;
|
||||
use crate::elf::*;
|
||||
use crate::input_files::FileId;
|
||||
@@ -230,6 +230,9 @@ fn for_each_entry<E: Target>(ctx: &Context<E>, mut define: impl FnMut(u32, u64))
|
||||
if E::FAMILY == Family::Arm64 && contains_variant_pcs(ctx) {
|
||||
define(DT_AARCH64_VARIANT_PCS, 0);
|
||||
}
|
||||
if note_property::is_bti(ctx) {
|
||||
define(DT_AARCH64_BTI_PLT, 0);
|
||||
}
|
||||
// RISC-V has the same feature but with a different name.
|
||||
if E::IS_RISCV && plt.symbols.iter().any(|&id| ctx.symbols[id].esym(ctx).riscv_variant_cc()) {
|
||||
define(DT_RISCV_VARIANT_CC, 0);
|
||||
@@ -243,7 +246,7 @@ fn for_each_entry<E: Target>(ctx: &Context<E>, mut define: impl FnMut(u32, u64))
|
||||
// it's what it is.
|
||||
define(
|
||||
DT_PPC64_GLINK,
|
||||
plt.hdr.shdr.sh_addr.get() + crate::chunks::plt::entry_offset::<E>(0) - 32,
|
||||
plt.hdr.shdr.sh_addr.get() + crate::chunks::plt::entry_offset(ctx, 0) - 32,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+49
-21
@@ -5,7 +5,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use crate::chunks::ChunkHeader;
|
||||
use crate::context::Context;
|
||||
use crate::elf::*;
|
||||
use crate::target::Target;
|
||||
use crate::target::{Family, Target};
|
||||
|
||||
// .note.gnu.property section contains an additional runtime information
|
||||
// about ISA variant.
|
||||
@@ -21,6 +21,11 @@ impl<E: Target> NotePropertySection<E> {
|
||||
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
|
||||
Self { hdr, contents: Vec::new() }
|
||||
}
|
||||
|
||||
/// The merged value of a property, or 0 if the output doesn't have it.
|
||||
pub fn get(&self, ty: u32) -> u32 {
|
||||
self.contents.iter().find(|&&(key, _)| key == ty).map_or(0, |&(_, val)| val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Target> Default for NotePropertySection<E> {
|
||||
@@ -33,8 +38,22 @@ fn entry_size<E: Target>() -> usize {
|
||||
if E::IS_64 { 16 } else { 12 }
|
||||
}
|
||||
|
||||
// Merges input files' .note.gnu.property values.
|
||||
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
|
||||
/// Whether the output is marked as compatible with ARM64 Branch Target
|
||||
/// Identification (BTI). The code of such a file is mapped to guarded pages
|
||||
/// in which an indirect branch must land on a `bti` instruction. Therefore,
|
||||
/// linker-synthesized code that may be reached by an indirect branch has
|
||||
/// to start with one.
|
||||
pub fn is_bti<E: Target>(ctx: &Context<E>) -> bool {
|
||||
E::FAMILY == Family::Arm64
|
||||
&& ctx.note_property.as_ref().is_some_and(|sec| {
|
||||
sec.get(GNU_PROPERTY_AARCH64_FEATURE_1_AND) & GNU_PROPERTY_AARCH64_FEATURE_1_BTI != 0
|
||||
})
|
||||
}
|
||||
|
||||
// Merges input files' .note.gnu.property values. This has to be done
|
||||
// before computing section sizes because the result affects the PLT
|
||||
// format on ARM64.
|
||||
pub fn construct<E: Target>(ctx: &mut Context<E>) {
|
||||
// Obtain the list of keys
|
||||
let files: Vec<&crate::input_files::ObjectFile<E>> =
|
||||
ctx.objs.iter().filter(|file| !ctx.is_internal(file.id())).collect();
|
||||
@@ -46,15 +65,21 @@ pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
|
||||
// Merge values for each key
|
||||
let mut map: BTreeMap<u32, u32> = BTreeMap::new();
|
||||
for key in keys {
|
||||
if (GNU_PROPERTY_X86_UINT32_AND_LO..=GNU_PROPERTY_X86_UINT32_AND_HI).contains(&key) {
|
||||
if (E::IS_X86
|
||||
&& (GNU_PROPERTY_X86_UINT32_AND_LO..=GNU_PROPERTY_X86_UINT32_AND_HI).contains(&key))
|
||||
|| (E::FAMILY == Family::Arm64 && key == GNU_PROPERTY_AARCH64_FEATURE_1_AND)
|
||||
{
|
||||
// An AND feature is set if all input objects have the property and
|
||||
// the feature.
|
||||
map.insert(key, files.iter().fold(u32::MAX, |acc, f| acc & value(f, key)));
|
||||
} else if (GNU_PROPERTY_X86_UINT32_OR_LO..=GNU_PROPERTY_X86_UINT32_OR_HI).contains(&key) {
|
||||
} else if E::IS_X86
|
||||
&& (GNU_PROPERTY_X86_UINT32_OR_LO..=GNU_PROPERTY_X86_UINT32_OR_HI).contains(&key)
|
||||
{
|
||||
// An OR feature is set if some input object has the feature.
|
||||
map.insert(key, files.iter().fold(0, |acc, f| acc | value(f, key)));
|
||||
} else if (GNU_PROPERTY_X86_UINT32_OR_AND_LO..=GNU_PROPERTY_X86_UINT32_OR_AND_HI)
|
||||
.contains(&key)
|
||||
} else if E::IS_X86
|
||||
&& (GNU_PROPERTY_X86_UINT32_OR_AND_LO..=GNU_PROPERTY_X86_UINT32_OR_AND_HI)
|
||||
.contains(&key)
|
||||
{
|
||||
// An OR-AND feature is set if all input object files have the property
|
||||
// and some of them has the feature.
|
||||
@@ -64,23 +89,26 @@ pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.args.z_ibt {
|
||||
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |= GNU_PROPERTY_X86_FEATURE_1_IBT;
|
||||
if E::IS_X86 {
|
||||
if ctx.args.z_ibt {
|
||||
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |=
|
||||
GNU_PROPERTY_X86_FEATURE_1_IBT;
|
||||
}
|
||||
if ctx.args.z_shstk {
|
||||
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |=
|
||||
GNU_PROPERTY_X86_FEATURE_1_SHSTK;
|
||||
}
|
||||
*map.entry(GNU_PROPERTY_X86_ISA_1_NEEDED).or_insert(0) |= ctx.args.z_x86_64_isa_level;
|
||||
}
|
||||
if ctx.args.z_shstk {
|
||||
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
|
||||
}
|
||||
*map.entry(GNU_PROPERTY_X86_ISA_1_NEEDED).or_insert(0) |= ctx.args.z_x86_64_isa_level;
|
||||
|
||||
// Serialize the map
|
||||
let contents: Vec<(u32, u32)> = map.into_iter().filter(|&(_, v)| v != 0).collect();
|
||||
ctx.note_property.as_mut().unwrap().contents =
|
||||
map.into_iter().filter(|&(_, v)| v != 0).collect();
|
||||
}
|
||||
|
||||
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
|
||||
let sec = ctx.note_property.as_mut().unwrap();
|
||||
sec.hdr.shdr.sh_size.set(if contents.is_empty() {
|
||||
0
|
||||
} else {
|
||||
(16 + contents.len() * entry_size::<E>()) as u64
|
||||
});
|
||||
sec.contents = contents;
|
||||
let n = sec.contents.len();
|
||||
sec.hdr.shdr.sh_size.set(if n == 0 { 0 } else { (16 + n * entry_size::<E>()) as u64 });
|
||||
}
|
||||
|
||||
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
|
||||
|
||||
+12
-5
@@ -2,7 +2,7 @@
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::chunks::ChunkHeader;
|
||||
use crate::chunks::{ChunkHeader, note_property};
|
||||
use crate::context::Context;
|
||||
use crate::elf::*;
|
||||
use crate::input_files::SymtabBlock;
|
||||
@@ -44,8 +44,15 @@ impl<E: Target> Default for PltSection<E> {
|
||||
// it switches to a "large" entry format. This is how many small entries fit.
|
||||
pub const SPARC_NUM_SMALL_PLT: u64 = (0x100000 - 128) / 32;
|
||||
|
||||
/// The size of a PLT entry, for targets whose entries are uniformly sized.
|
||||
pub fn entry_size<E: Target>(ctx: &Context<E>) -> u64 {
|
||||
// ARM64 PLT entries grow from 16 to 24 bytes to make room for a `bti c`
|
||||
// landing pad if BTI is enabled.
|
||||
if note_property::is_bti(ctx) { 24 } else { E::PLT_SIZE }
|
||||
}
|
||||
|
||||
/// The offset of a PLT entry within `.plt`.
|
||||
pub fn entry_offset<E: Target>(idx: u32) -> u64 {
|
||||
pub fn entry_offset<E: Target>(ctx: &Context<E>, idx: u32) -> u64 {
|
||||
let idx = idx as u64;
|
||||
match E::FAMILY {
|
||||
Family::Ppc64V1 => {
|
||||
@@ -71,7 +78,7 @@ pub fn entry_offset<E: Target>(idx: u32) -> u64 {
|
||||
0x100000 + (i / 160) * 5120 + (i % 160) * 24
|
||||
}
|
||||
}
|
||||
_ => E::PLT_HDR_SIZE + idx * E::PLT_SIZE,
|
||||
_ => E::PLT_HDR_SIZE + idx * entry_size(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,14 +99,14 @@ pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
|
||||
} else if E::IS_SPARC {
|
||||
E::PLT_HDR_SIZE + n * E::PLT_SIZE
|
||||
} else {
|
||||
entry_offset::<E>(n as u32)
|
||||
entry_offset(ctx, n as u32)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
|
||||
E::write_plt_header(ctx, buf);
|
||||
for (i, &id) in ctx.plt.symbols.iter().enumerate() {
|
||||
let off = entry_offset::<E>(i as u32) as usize;
|
||||
let off = entry_offset(ctx, i as u32) as usize;
|
||||
E::write_plt_entry(ctx, &mut buf[off..], &ctx.symbols[id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +334,10 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
|
||||
if ctx.args.z_execstack_if_needed && ctx.objs.iter().any(|f| f.needs_executable_stack) {
|
||||
ctx.args.z_execstack = true;
|
||||
}
|
||||
// Merge .note.gnu.property contents of input files.
|
||||
if ctx.note_property.is_some() {
|
||||
chunks::note_property::construct(&mut ctx);
|
||||
}
|
||||
|
||||
// If we are linking a .so file, remaining undefined symbols does
|
||||
// not cause a linker error. Instead, they are treated as if they
|
||||
|
||||
@@ -219,6 +219,7 @@ pub const DT_VERNEEDNUM: u32 = 0x6fffffff;
|
||||
pub const DT_PPC_GOT: u32 = 0x70000000;
|
||||
pub const DT_PPC64_GLINK: u32 = 0x70000000;
|
||||
pub const DT_RISCV_VARIANT_CC: u32 = 0x70000001;
|
||||
pub const DT_AARCH64_BTI_PLT: u32 = 0x70000001;
|
||||
pub const DT_AARCH64_VARIANT_PCS: u32 = 0x70000005;
|
||||
pub const DT_AUXILIARY: u32 = 0x7ffffffd;
|
||||
pub const DT_FILTER: u32 = 0x7fffffff;
|
||||
@@ -263,6 +264,9 @@ pub const GNU_PROPERTY_X86_ISA_1_V2: u32 = 2;
|
||||
pub const GNU_PROPERTY_X86_ISA_1_V3: u32 = 4;
|
||||
pub const GNU_PROPERTY_X86_ISA_1_V4: u32 = 8;
|
||||
|
||||
pub const GNU_PROPERTY_AARCH64_FEATURE_1_AND: u32 = 0xc0000000;
|
||||
pub const GNU_PROPERTY_AARCH64_FEATURE_1_BTI: u32 = 1;
|
||||
|
||||
pub const ELFCOMPRESS_NONE: u32 = 0;
|
||||
pub const ELFCOMPRESS_ZLIB: u32 = 1;
|
||||
pub const ELFCOMPRESS_ZSTD: u32 = 2;
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ pub fn create_synthetic_sections<E: Target>(ctx: &mut Context<E>) {
|
||||
chunks.push(ChunkId::Verneed);
|
||||
chunks.push(ChunkId::NotePackage);
|
||||
|
||||
if E::IS_X86 {
|
||||
if E::IS_X86 || E::FAMILY == Family::Arm64 {
|
||||
ctx.note_property = Some(NotePropertySection::<E>::new());
|
||||
chunks.push(ChunkId::NoteProperty);
|
||||
}
|
||||
|
||||
+5
-2
@@ -43,7 +43,7 @@ use crate::input_files::FileId;
|
||||
use crate::input_sections::InputSectionId;
|
||||
use crate::output_file::OutputFile;
|
||||
use crate::passes;
|
||||
use crate::target::Target;
|
||||
use crate::target::{Family, Target};
|
||||
use crate::util::align_to;
|
||||
|
||||
/// An output section's sh_link can refer to only one section, so
|
||||
@@ -109,7 +109,7 @@ fn create_synthetic_sections<E: Target>(ctx: &mut Context<E>) {
|
||||
ChunkId::Symtab,
|
||||
ChunkId::Shstrtab,
|
||||
]);
|
||||
if E::IS_X86 {
|
||||
if E::IS_X86 || E::FAMILY == Family::Arm64 {
|
||||
ctx.note_property = Some(NotePropertySection::<E>::new());
|
||||
ctx.chunks.push(ChunkId::NoteProperty);
|
||||
}
|
||||
@@ -214,6 +214,9 @@ pub fn combine_objects<E: Target>(ctx: &mut Context<E>) {
|
||||
split_link_order_sections(ctx);
|
||||
create_synthetic_sections(ctx);
|
||||
claim_unresolved_symbols(ctx);
|
||||
if ctx.note_property.is_some() {
|
||||
chunks::note_property::construct(ctx);
|
||||
}
|
||||
passes::compute_section_sizes(ctx);
|
||||
passes::sort_output_sections(ctx);
|
||||
passes::create_output_symtab(ctx);
|
||||
|
||||
+1
-1
@@ -1135,7 +1135,7 @@ impl Symbol {
|
||||
#[inline]
|
||||
pub fn plt_addr<E: Target>(&self, ctx: &Context<E>) -> u64 {
|
||||
if let Some(idx) = self.plt_idx(&ctx.symbols) {
|
||||
return ctx.plt.hdr.shdr.sh_addr.get() + crate::chunks::plt::entry_offset::<E>(idx);
|
||||
return ctx.plt.hdr.shdr.sh_addr.get() + crate::chunks::plt::entry_offset(ctx, idx);
|
||||
}
|
||||
ctx.pltgot.hdr.shdr.sh_addr.get()
|
||||
+ self.pltgot_idx(&ctx.symbols).unwrap() as u64 * E::PLTGOT_SIZE
|
||||
|
||||
+48
-11
@@ -20,7 +20,7 @@
|
||||
//! Instructions are little-endian even on big-endian targets, where only
|
||||
//! data is byte-swapped.
|
||||
|
||||
use crate::chunks::eh_frame;
|
||||
use crate::chunks::{eh_frame, note_property, plt};
|
||||
use crate::context::Context;
|
||||
use crate::elf::*;
|
||||
use crate::input_sections::NonAllocReloc;
|
||||
@@ -94,8 +94,31 @@ fn is_add(loc: &[u8]) -> bool {
|
||||
}
|
||||
|
||||
const NOP: u32 = 0xd503_201f;
|
||||
const BTI_C: u32 = 0xd503_245f;
|
||||
|
||||
/// Writes a `bti c` to the beginning of `buf` if `needed` is true. Returns
|
||||
/// the offset of the next instruction.
|
||||
fn write_landing_pad(buf: &mut [u8], needed: bool) -> usize {
|
||||
if needed {
|
||||
write_insn(buf, BTI_C);
|
||||
4
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LE: bool> Arm64Target<LE> {
|
||||
/// Whether a symbol's PLT entry needs a BTI landing pad. A PLT entry is
|
||||
/// usually reached only by direct calls, but its address is used as the
|
||||
/// function's address if the PLT is canonical or belongs to an ifunc,
|
||||
/// and a range extension thunk jumps to it with an indirect branch.
|
||||
fn plt_needs_landing_pad(ctx: &Context<Self>, sym: &Symbol) -> bool {
|
||||
note_property::is_bti(ctx)
|
||||
&& (sym.is_canonical()
|
||||
|| sym.is_ifunc()
|
||||
|| sym.aux(&ctx.symbols).is_some_and(|aux| !aux.thunk_addrs.is_empty()))
|
||||
}
|
||||
|
||||
/// Whether the ADRP+ADD pair at relocation `i` can become NOP+ADR,
|
||||
/// which the psABI allows when the target is within ±1 MiB.
|
||||
fn relaxes_adrp_add(
|
||||
@@ -187,28 +210,39 @@ impl<const LE: bool> Target for Arm64Target<LE> {
|
||||
0xd420_7d00, // brk
|
||||
0xd420_7d00, // brk
|
||||
];
|
||||
for (i, &v) in INSN.iter().enumerate() {
|
||||
write_insn(&mut buf[i * 4..], v);
|
||||
// PLT entries jump to the header with an indirect branch. A landing
|
||||
// pad, if any, replaces one of the trailing brks.
|
||||
let off = write_landing_pad(buf, note_property::is_bti(ctx));
|
||||
let buf = &mut buf[off..Self::PLT_HDR_SIZE as usize];
|
||||
for (loc, &v) in buf.chunks_exact_mut(4).zip(&INSN) {
|
||||
write_insn(loc, v);
|
||||
}
|
||||
let gotplt = ctx.gotplt.shdr.sh_addr.get() + 16;
|
||||
let plt = ctx.plt.hdr.shdr.sh_addr.get();
|
||||
let plt = ctx.plt.hdr.shdr.sh_addr.get() + off as u64;
|
||||
write_adrp(&mut buf[4..], page(gotplt).wrapping_sub(page(plt + 4)));
|
||||
or_insn(&mut buf[8..], (bits(gotplt, 11, 3) << 10) as u32);
|
||||
or_insn(&mut buf[12..], ((gotplt & 0xfff) << 10) as u32);
|
||||
}
|
||||
|
||||
// A PLT entry is 16 bytes long by default and 24 bytes long if BTI is
|
||||
// enabled. The extra space holds a `bti c` if the entry needs one, and
|
||||
// the rest of the entry is filled with brks.
|
||||
fn write_plt_entry(ctx: &Context<Self>, buf: &mut [u8], sym: &Symbol) {
|
||||
const INSN: [u32; 4] = [
|
||||
const INSN: [u32; 6] = [
|
||||
0x9000_0010, // adrp x16, .got.plt[n]
|
||||
0xf940_0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100_0210, // add x16, x16, .got.plt[n]
|
||||
0xd61f_0220, // br x17
|
||||
0xd420_7d00, // brk
|
||||
0xd420_7d00, // brk
|
||||
];
|
||||
for (i, &v) in INSN.iter().enumerate() {
|
||||
write_insn(&mut buf[i * 4..], v);
|
||||
let off = write_landing_pad(buf, Self::plt_needs_landing_pad(ctx, sym));
|
||||
let buf = &mut buf[off..plt::entry_size(ctx) as usize];
|
||||
for (loc, &v) in buf.chunks_exact_mut(4).zip(&INSN) {
|
||||
write_insn(loc, v);
|
||||
}
|
||||
let gotplt = sym.gotplt_addr(ctx);
|
||||
let plt = sym.plt_addr(ctx);
|
||||
let plt = sym.plt_addr(ctx) + off as u64;
|
||||
write_adrp(buf, page(gotplt).wrapping_sub(page(plt)));
|
||||
or_insn(&mut buf[4..], (bits(gotplt, 11, 3) << 10) as u32);
|
||||
or_insn(&mut buf[8..], ((gotplt & 0xfff) << 10) as u32);
|
||||
@@ -221,11 +255,14 @@ impl<const LE: bool> Target for Arm64Target<LE> {
|
||||
0xd61f_0220, // br x17
|
||||
0xd420_7d00, // brk
|
||||
];
|
||||
for (i, &v) in INSN.iter().enumerate() {
|
||||
write_insn(&mut buf[i * 4..], v);
|
||||
// A landing pad, if any, replaces the trailing brk.
|
||||
let off = write_landing_pad(buf, Self::plt_needs_landing_pad(ctx, sym));
|
||||
let buf = &mut buf[off..Self::PLTGOT_SIZE as usize];
|
||||
for (loc, &v) in buf.chunks_exact_mut(4).zip(&INSN) {
|
||||
write_insn(loc, v);
|
||||
}
|
||||
let got = sym.got_pltgot_addr(ctx);
|
||||
let plt = sym.plt_addr(ctx);
|
||||
let plt = sym.plt_addr(ctx) + off as u64;
|
||||
write_adrp(buf, page(got).wrapping_sub(page(plt)));
|
||||
or_insn(&mut buf[4..], (bits(got, 11, 3) << 10) as u32);
|
||||
}
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
test_cflags -mbranch-protection=bti || skip
|
||||
|
||||
cat <<EOF | $CC -fPIC -mbranch-protection=bti -c -o $t/a.o -xc -
|
||||
int foo() { return 1; }
|
||||
int bar() { return 2; }
|
||||
int baz() { return 3; }
|
||||
int call(int (*fn)()) { return fn(); }
|
||||
EOF
|
||||
|
||||
$CC -B. -shared -nostdlib -o $t/b.so $t/a.o
|
||||
|
||||
# All PLT entries jump to the PLT header indirectly. Other than that, a
|
||||
# PLT entry needs a landing pad only if it's reached by a range extension
|
||||
# thunk (foo and call), if it's a canonical PLT (baz) or if it belongs to
|
||||
# an ifunc (qux).
|
||||
cat <<EOF | $CC -fno-PIC -mbranch-protection=bti -c -o $t/c.o -xc -
|
||||
int foo();
|
||||
int bar();
|
||||
int baz();
|
||||
int call(int (*fn)());
|
||||
|
||||
static int impl() { return 4; }
|
||||
static void *resolve() { return impl; }
|
||||
int qux() __attribute__((ifunc("resolve")));
|
||||
|
||||
__attribute__((section(".low"))) int low() { return bar(); }
|
||||
|
||||
void _start() {
|
||||
register long x0 __asm__("x0") = foo() + low() + call(baz) + call(qux) == 10 ? 0 : 1;
|
||||
register long x8 __asm__("x8") = 93; // exit
|
||||
__asm__ volatile("svc 0" :: "r"(x0), "r"(x8));
|
||||
}
|
||||
EOF
|
||||
|
||||
$CC -B. -no-pie -nostdlib -o $t/exe $t/c.o $t/b.so \
|
||||
-Wl,--section-start=.low=0x10000000,--section-start=.text=0x20000000
|
||||
|
||||
readelf -n $t/exe | grep 'AArch64 feature: BTI'
|
||||
readelf --dynamic $t/exe | grep AARCH64_BTI_PLT
|
||||
|
||||
$OBJDUMP -d -j .plt -j .plt.got $t/exe > $t/log
|
||||
grep -A1 '<_PROCEDURE_LINKAGE_TABLE_>:' $t/log | grep -w bti
|
||||
grep -A1 -E '<foo[@$]plt>:' $t/log | grep -w bti
|
||||
grep -A1 -E '<bar[@$]plt>:' $t/log | not grep -w bti
|
||||
grep -A1 -E '<baz[@$]plt>:' $t/log | grep -w bti
|
||||
grep -A1 -E '<call[@$]plt>:' $t/log | grep -w bti
|
||||
grep -A1 '<qux$pltgot>:' $t/log | grep -w bti
|
||||
|
||||
$QEMU $t/exe
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
test_cflags -mbranch-protection=standard || skip
|
||||
|
||||
cat <<EOF | $CC -mbranch-protection=standard -c -o $t/a.o -xc -
|
||||
void _start() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -mbranch-protection=standard -c -o $t/b.o -xc -
|
||||
void foo() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -mbranch-protection=bti -c -o $t/c.o -xc -
|
||||
void bar() {}
|
||||
EOF
|
||||
|
||||
cat <<EOF | $CC -mbranch-protection=none -c -o $t/d.o -xc -
|
||||
void baz() {}
|
||||
EOF
|
||||
|
||||
./mold -o $t/exe1 $t/a.o $t/b.o
|
||||
readelf -n $t/exe1 | grep 'AArch64 feature: BTI, PAC'
|
||||
readelf -W --segments $t/exe1 | grep -w GNU_PROPERTY
|
||||
|
||||
./mold -o $t/exe2 $t/a.o $t/b.o $t/c.o
|
||||
readelf -n $t/exe2 | grep 'AArch64 feature: BTI$'
|
||||
|
||||
./mold -o $t/exe3 $t/a.o $t/b.o $t/c.o $t/d.o
|
||||
readelf -n $t/exe3 | not grep 'AArch64 feature'
|
||||
|
||||
./mold -r -o $t/e.o $t/a.o $t/b.o
|
||||
readelf -n $t/e.o | grep 'AArch64 feature: BTI, PAC'
|
||||
Reference in new issue
Block a user