Skip to content

Commit 86eb76f

Browse files
Rollup merge of #157445 - pmur:murp/extend-patchable, r=mejrs
Allow section override when using patchable-function-entries Sometimes it is necessary to group patchable function entrypoint records in distinct linker sections. This is the case for some bpf functions within the linux kernel which shouldn't be visible to ftrace. Extend `-Zpatchable-function-entry` to accept an argument of the form `prefix_nops,total_nops,record_section`, which places all entry record into a user specified section. Likewise, extend the `patchable_function_entry` attribute to accept an optional `section="name"` option to place a function into a specific section. This is made possible by llvm attribute `patchable-function-entry-section` added in llvm 21.
2 parents cd1d41e + c340e8a commit 86eb76f

18 files changed

Lines changed: 257 additions & 74 deletions

File tree

compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use rustc_span::edition::Edition::Edition2024;
88
use super::prelude::*;
99
use crate::attributes::AttributeSafety;
1010
use crate::session_diagnostics::{
11-
EmptyExportName, NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass,
12-
NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
13-
SanitizeInvalidStatic, TargetFeatureOnLangItem,
11+
EmptyExportName, EmptySection, NakedFunctionIncompatibleAttribute, NullOnExport,
12+
NullOnObjcClass, NullOnObjcSelector, NullOnSection, ObjcClassExpectedStringLiteral,
13+
ObjcSelectorExpectedStringLiteral, SanitizeInvalidStatic, TargetFeatureOnLangItem,
1414
};
1515
use crate::target_checking::Policy::AllowSilent;
1616

@@ -795,82 +795,94 @@ pub(crate) struct PatchableFunctionEntryParser;
795795
impl SingleAttributeParser for PatchableFunctionEntryParser {
796796
const PATH: &[Symbol] = &[sym::patchable_function_entry];
797797
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
798-
const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]);
798+
const TEMPLATE: AttributeTemplate =
799+
template!(List: &["prefix_nops = m, entry_nops = n, section = \"section\""]);
799800
const STABILITY: AttributeStability = unstable!(patchable_function_entry);
800801

801802
fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<AttributeKind> {
802803
let meta_item_list = cx.expect_list(args, cx.attr_span)?;
803804

804805
let mut prefix = None;
805806
let mut entry = None;
807+
let mut section = None;
806808

807809
if meta_item_list.len() == 0 {
808810
cx.adcx().expected_at_least_one_argument(meta_item_list.span);
809811
return None;
810812
}
811813

812-
let mut errored = false;
813-
814814
for item in meta_item_list.mixed() {
815815
let Some((ident, value)) = cx.expect_name_value(item, item.span(), None) else {
816-
continue;
816+
return None;
817817
};
818818

819819
let attrib_to_write = match ident.name {
820820
sym::prefix_nops => {
821821
// Duplicate prefixes are not allowed
822822
if prefix.is_some() {
823-
errored = true;
824823
cx.adcx().duplicate_key(ident.span, sym::prefix_nops);
825-
continue;
824+
return None;
826825
}
827826
&mut prefix
828827
}
829828
sym::entry_nops => {
830829
// Duplicate entries are not allowed
831830
if entry.is_some() {
832-
errored = true;
833831
cx.adcx().duplicate_key(ident.span, sym::entry_nops);
834-
continue;
832+
return None;
835833
}
836834
&mut entry
837835
}
836+
sym::section => {
837+
// Duplicate entries are not allowed
838+
if section.is_some() {
839+
cx.adcx().duplicate_key(ident.span, sym::section);
840+
return None;
841+
}
842+
// Only a string type value is allowed.
843+
let Some(value_str) = value.value_as_str() else {
844+
cx.adcx().expect_string_literal(value);
845+
return None;
846+
};
847+
// The section name does not allow null characters.
848+
if value_str.as_str().contains('\0') {
849+
cx.emit_err(NullOnSection { span: value.value_span });
850+
}
851+
// The section name is not allowed to be empty, LLVM does
852+
// not allow them.
853+
if value_str.is_empty() {
854+
cx.emit_err(EmptySection { span: value.value_span });
855+
}
856+
section = Some(value_str);
857+
// Integer parsing is not needed, process next item.
858+
continue;
859+
}
838860
_ => {
839-
errored = true;
840861
cx.adcx().expected_specific_argument(
841862
ident.span,
842863
&[sym::prefix_nops, sym::entry_nops],
843864
);
844-
continue;
865+
return None;
845866
}
846867
};
847868

848869
let rustc_ast::LitKind::Int(val, _) = value.value_as_lit().kind else {
849-
errored = true;
850870
cx.adcx().expected_integer_literal(value.value_span);
851-
continue;
871+
return None;
852872
};
853873

854874
let Ok(val) = val.get().try_into() else {
855-
errored = true;
856875
cx.adcx().expected_integer_literal_in_range(
857876
value.value_span,
858877
u8::MIN as isize,
859878
u8::MAX as isize,
860879
);
861-
continue;
880+
return None;
862881
};
863882

864883
*attrib_to_write = Some(val);
865884
}
866885

867-
if errored {
868-
None
869-
} else {
870-
Some(AttributeKind::PatchableFunctionEntry {
871-
prefix: prefix.unwrap_or(0),
872-
entry: entry.unwrap_or(0),
873-
})
874-
}
886+
Some(AttributeKind::PatchableFunctionEntry { prefix, entry, section })
875887
}
876888
}

compiler/rustc_attr_parsing/src/session_diagnostics.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ pub(crate) struct EmptyExportName {
292292
pub span: Span,
293293
}
294294

295+
#[derive(Diagnostic)]
296+
#[diag("`section` may not be empty")]
297+
pub(crate) struct EmptySection {
298+
#[primary_span]
299+
pub span: Span,
300+
}
301+
295302
#[derive(Diagnostic)]
296303
#[diag("`export_name` may not contain null characters", code = E0648)]
297304
pub(crate) struct NullOnExport {
@@ -327,6 +334,13 @@ pub(crate) struct NullOnObjcSelector {
327334
pub span: Span,
328335
}
329336

337+
#[derive(Diagnostic)]
338+
#[diag("`section` may not contain null characters", code = E0648)]
339+
pub(crate) struct NullOnSection {
340+
#[primary_span]
341+
pub span: Span,
342+
}
343+
330344
#[derive(Diagnostic)]
331345
#[diag("`objc::class!` expected a string literal")]
332346
pub(crate) struct ObjcClassExpectedStringLiteral {

compiler/rustc_codegen_llvm/src/attributes.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,26 @@ fn patchable_function_entry_attrs<'ll>(
8686
attr: Option<PatchableFunctionEntry>,
8787
) -> SmallVec<[&'ll Attribute; 2]> {
8888
let mut attrs = SmallVec::new();
89-
let patchable_spec = attr.unwrap_or_else(|| {
90-
PatchableFunctionEntry::from_config(sess.opts.unstable_opts.patchable_function_entry)
91-
});
92-
let entry = patchable_spec.entry();
93-
let prefix = patchable_spec.prefix();
89+
90+
let mut entry = sess.opts.unstable_opts.patchable_function_entry.entry();
91+
let mut prefix = sess.opts.unstable_opts.patchable_function_entry.prefix();
92+
let mut section = sess.opts.unstable_opts.patchable_function_entry.section();
93+
let section_sym;
94+
95+
// Apply attribute specified overrides, if any.
96+
if let Some(patchable_spec) = attr {
97+
if let Some(sym) = patchable_spec.section() {
98+
section_sym = sym;
99+
section = Some(section_sym.as_str());
100+
}
101+
// Override the nop counts if either is present. If only one is present, the
102+
// other count is implied to be 0.
103+
if patchable_spec.entry().is_some() || patchable_spec.prefix().is_some() {
104+
entry = patchable_spec.entry().unwrap_or(0);
105+
prefix = patchable_spec.prefix().unwrap_or(0);
106+
}
107+
}
108+
94109
if entry > 0 {
95110
attrs.push(llvm::CreateAttrStringValue(
96111
cx.llcx,
@@ -105,6 +120,13 @@ fn patchable_function_entry_attrs<'ll>(
105120
&format!("{}", prefix),
106121
));
107122
}
123+
if let Some(section) = section {
124+
attrs.push(llvm::CreateAttrStringValue(
125+
cx.llcx,
126+
"patchable-function-entry-section",
127+
section,
128+
));
129+
}
108130
attrs
109131
}
110132

compiler/rustc_codegen_llvm/src/context.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use rustc_data_structures::base_n::{ALPHANUMERIC_ONLY, ToBaseN};
1414
use rustc_data_structures::fx::FxHashMap;
1515
use rustc_data_structures::small_c_str::SmallCStr;
1616
use rustc_hir::def_id::DefId;
17-
use rustc_middle::middle::codegen_fn_attrs::PatchableFunctionEntry;
1817
use rustc_middle::mono::CodegenUnit;
1918
use rustc_middle::ty::layout::{
2019
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
@@ -343,14 +342,13 @@ pub(crate) unsafe fn create_module<'ll>(
343342

344343
// Add "kcfi-offset" module flag with -Z patchable-function-entry (See
345344
// https://reviews.llvm.org/D141172).
346-
let pfe =
347-
PatchableFunctionEntry::from_config(sess.opts.unstable_opts.patchable_function_entry);
348-
if pfe.prefix() > 0 {
345+
let patchable_prefix_nops = sess.opts.unstable_opts.patchable_function_entry.prefix();
346+
if patchable_prefix_nops > 0 {
349347
llvm::add_module_flag_u32(
350348
llmod,
351349
llvm::ModuleFlagMergeBehavior::Override,
352350
"kcfi-offset",
353-
pfe.prefix().into(),
351+
patchable_prefix_nops.into(),
354352
);
355353
}
356354

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,11 @@ fn process_builtin_attrs(
290290
AttributeKind::RustcOffloadKernel => {
291291
codegen_fn_attrs.flags |= CodegenFnAttrFlags::OFFLOAD_KERNEL
292292
}
293-
AttributeKind::PatchableFunctionEntry { prefix, entry } => {
293+
AttributeKind::PatchableFunctionEntry { prefix, entry, section } => {
294294
codegen_fn_attrs.patchable_function_entry =
295-
Some(PatchableFunctionEntry::from_prefix_and_entry(*prefix, *entry));
295+
Some(PatchableFunctionEntry::from_prefix_entry_and_section(
296+
*prefix, *entry, *section,
297+
));
296298
}
297299
AttributeKind::InstrumentFn(instrument_fn) => {
298300
codegen_fn_attrs.instrument_fn = match instrument_fn {

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,8 +1271,9 @@ pub enum AttributeKind {
12711271

12721272
/// Represents `#[patchable_function_entry]`
12731273
PatchableFunctionEntry {
1274-
prefix: u8,
1275-
entry: u8,
1274+
prefix: Option<u8>,
1275+
entry: Option<u8>,
1276+
section: Option<Symbol>,
12761277
},
12771278

12781279
/// Represents `#[path]`

compiler/rustc_interface/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ fn test_unstable_options_tracking_hash() {
866866
tracked!(panic_in_drop, PanicStrategy::Abort);
867867
tracked!(
868868
patchable_function_entry,
869-
PatchableFunctionEntry::from_total_and_prefix_nops(10, 5)
869+
PatchableFunctionEntry::from_parts(10, 5, None)
870870
.expect("total must be greater than or equal to prefix")
871871
);
872872
tracked!(plt, Some(true));

compiler/rustc_middle/src/middle/codegen_fn_attrs.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub struct CodegenFnAttrs {
114114
// FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity
115115
pub alignment: Option<Align>,
116116
/// The `#[patchable_function_entry(...)]` attribute. Indicates how many nops should be around
117-
/// the function entry.
117+
/// the function entry, or override default section to record entry location.
118118
pub patchable_function_entry: Option<PatchableFunctionEntry>,
119119
/// The `#[rustc_objc_class = "..."]` attribute.
120120
pub objc_class: Option<Symbol>,
@@ -162,24 +162,33 @@ pub struct TargetFeature {
162162
#[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, StableHash)]
163163
pub struct PatchableFunctionEntry {
164164
/// Nops to prepend to the function
165-
prefix: u8,
165+
prefix: Option<u8>,
166166
/// Nops after entry, but before body
167-
entry: u8,
167+
entry: Option<u8>,
168+
/// Optional, specific section to record entry location in
169+
section: Option<Symbol>,
168170
}
169171

170172
impl PatchableFunctionEntry {
171-
pub fn from_config(config: rustc_session::config::PatchableFunctionEntry) -> Self {
172-
Self { prefix: config.prefix(), entry: config.entry() }
173+
pub fn from_prefix_entry_and_section(
174+
prefix: Option<u8>,
175+
entry: Option<u8>,
176+
section: Option<Symbol>,
177+
) -> Self {
178+
Self { prefix, entry, section }
173179
}
174180
pub fn from_prefix_and_entry(prefix: u8, entry: u8) -> Self {
175-
Self { prefix, entry }
181+
Self { prefix: Some(prefix), entry: Some(entry), section: None }
176182
}
177-
pub fn prefix(&self) -> u8 {
183+
pub fn prefix(&self) -> Option<u8> {
178184
self.prefix
179185
}
180-
pub fn entry(&self) -> u8 {
186+
pub fn entry(&self) -> Option<u8> {
181187
self.entry
182188
}
189+
pub fn section(&self) -> Option<Symbol> {
190+
self.section
191+
}
183192
}
184193

185194
#[derive(Clone, Copy, PartialEq, Eq, TyEncodable, TyDecodable, StableHash)]

compiler/rustc_session/src/config.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3342,23 +3342,29 @@ impl DumpMonoStatsFormat {
33423342

33433343
/// `-Z patchable-function-entry` representation - how many nops to put before and after function
33443344
/// entry.
3345-
#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)]
3345+
#[derive(Clone, PartialEq, Hash, Debug, Default)]
33463346
pub struct PatchableFunctionEntry {
33473347
/// Nops before the entry
33483348
prefix: u8,
33493349
/// Nops after the entry
33503350
entry: u8,
3351+
/// An optional section name to record the entry location
3352+
section: Option<String>,
33513353
}
33523354

33533355
impl PatchableFunctionEntry {
3354-
pub fn from_total_and_prefix_nops(
3356+
pub fn from_parts(
33553357
total_nops: u8,
33563358
prefix_nops: u8,
3359+
section: Option<String>,
33573360
) -> Option<PatchableFunctionEntry> {
33583361
if total_nops < prefix_nops {
33593362
None
3363+
// Section name cannot contain null characters.
3364+
} else if section.as_ref().map(|x| x.contains('\0') || x.is_empty()).unwrap_or(false) {
3365+
None
33603366
} else {
3361-
Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops })
3367+
Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops, section })
33623368
}
33633369
}
33643370
pub fn prefix(&self) -> u8 {
@@ -3367,6 +3373,9 @@ impl PatchableFunctionEntry {
33673373
pub fn entry(&self) -> u8 {
33683374
self.entry
33693375
}
3376+
pub fn section(&self) -> Option<&str> {
3377+
self.section.as_ref().map(|x| x.as_str())
3378+
}
33703379
}
33713380

33723381
/// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy,

compiler/rustc_session/src/options.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ mod desc {
784784
pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`";
785785
pub(crate) const parse_panic_strategy: &str = "either `unwind`, `abort`, or `immediate-abort`";
786786
pub(crate) const parse_on_broken_pipe: &str = "either `kill`, `error`, or `inherit`";
787-
pub(crate) const parse_patchable_function_entry: &str = "either two comma separated integers (total_nops,prefix_nops), with prefix_nops <= total_nops, or one integer (total_nops)";
787+
pub(crate) const parse_patchable_function_entry: &str = "a comma separated list of (prefix_nops,total_nops,section_name), (prefix_nops,total_nops), or (total_nops). Where prefix_nops <= total_nops where 0 < total_nops <= 255 and prefix_nops <= total_nops";
788788
pub(crate) const parse_opt_panic_strategy: &str = parse_panic_strategy;
789789
pub(crate) const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
790790
pub(crate) const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `kernel-hwaddress`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, `thread`, or 'realtime'";
@@ -1206,20 +1206,24 @@ pub mod parse {
12061206
) -> bool {
12071207
let mut total_nops = 0;
12081208
let mut prefix_nops = 0;
1209+
let mut section = None;
12091210

12101211
if !parse_number(&mut total_nops, v) {
1211-
let parts = v.and_then(|v| v.split_once(',')).unzip();
1212-
if !parse_number(&mut total_nops, parts.0) {
1212+
let parts: Vec<_> = v.unwrap_or("").split(',').collect();
1213+
if parts.len() < 2 || parts.len() > 3 {
12131214
return false;
12141215
}
1215-
if !parse_number(&mut prefix_nops, parts.1) {
1216+
1217+
if !parse_number(&mut total_nops, Some(parts[0])) {
1218+
return false;
1219+
}
1220+
if !parse_number(&mut prefix_nops, Some(parts[1])) {
12161221
return false;
12171222
}
1223+
section = parts.get(2).map(|x| x.to_string());
12181224
}
12191225

1220-
if let Some(pfe) =
1221-
PatchableFunctionEntry::from_total_and_prefix_nops(total_nops, prefix_nops)
1222-
{
1226+
if let Some(pfe) = PatchableFunctionEntry::from_parts(total_nops, prefix_nops, section) {
12231227
*slot = pfe;
12241228
return true;
12251229
}

0 commit comments

Comments
 (0)