From 6f58e4ab8d90166cac4e4da269a77c5134f12f1e Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Thu, 9 Jul 2026 22:07:47 +0200 Subject: Add support for the asan:fixed-segments comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This magic comment allows for the definition of segments that are fixed and for which references are always safe. This goes in tandem with the asan:safe comment, which can then be used more sporadically. Signed-off-by: Miquel Sabaté Solà --- lib/xixanta/src/assembler.rs | 81 +++++++++++++++++++-------- lib/xixanta/src/node.rs | 2 + lib/xixanta/src/parser.rs | 130 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 24 deletions(-) (limited to 'lib/xixanta/src') diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs index 98ae19d..26d12b2 100644 --- a/lib/xixanta/src/assembler.rs +++ b/lib/xixanta/src/assembler.rs @@ -184,6 +184,10 @@ struct Assembler<'a> { // is a tuple where the first member is the String identifier of the // variable/address/label/etc. that is being stored in the second member. objects_visited: Vec<(String, Object)>, + + // List of mapping/segment indeces which the programmer asked to be + // considered safe. + safe_segments: Vec<(usize, usize)>, } /// The result to be given at the end of `assembler::assemble` and @@ -453,6 +457,7 @@ impl<'a> Assembler<'a> { macro_context: vec![], pending_defines: vec![], objects_visited: vec![], + safe_segments: vec![], } } @@ -950,6 +955,14 @@ impl<'a> Assembler<'a> { next_safe = true; self.asan_next_safe = true; } + NodeType::Comment(CommentType::AsanFixedSegments(segments)) => { + for name in segments { + let (midx, sidx) = self.get_mapping_segment_by_name(node, name)?; + if !self.is_in_safe_segment(midx, sidx) { + self.safe_segments.push((midx, sidx)); + } + } + } NodeType::Instruction => { self.literal_mode = None; match self.evaluate_node(node) { @@ -1107,7 +1120,7 @@ impl<'a> Assembler<'a> { bundle.address = current.bundles[pn.bundle_index].address; // The first check deals on whether there is any reference - // that crosses the mapping boundary. There are two + // that crosses the mapping boundary. There are some // exceptions to this check: // // 1. We are currently on the Vector segment, which @@ -1115,6 +1128,9 @@ impl<'a> Assembler<'a> { // that is to be expected and safe. // 2. If the bundle was marked as "safe" by the programmer // via the asan:safe/check:safe comment. + // 3. The object belongs to a segment that the programmer + // explicitely marked as safe via the + // asan:fixed-segments/check:fixed-segments comment. // // Otherwise iterate over the objects that were visited over // the previous evaluate_node() call, and check for @@ -1125,7 +1141,9 @@ impl<'a> Assembler<'a> { && !matches!(self.mappings[pn.mapping].section_type, SectionType::Vector) { for (name, object) in &self.objects_visited { - if pn.mapping != object.mapping { + if pn.mapping != object.mapping + && !self.is_in_safe_segment(object.mapping, object.segment) + { let reference = &self.mappings[object.mapping].segments[object.segment].name; @@ -2925,6 +2943,39 @@ impl<'a> Assembler<'a> { Ok(()) } + // Returns true if the given mapping/segment pair were marked by the + // programmer to be safe. + fn is_in_safe_segment(&self, mapping: usize, segment: usize) -> bool { + self.safe_segments + .iter() + .any(|s| s.0 == mapping && s.1 == segment) + } + + // Return the mapping and segment indeces which identify the given segment + // identified by 'name'. If it cannot be found, then it returns an error by + // pinning the location of the given 'node'. + fn get_mapping_segment_by_name( + &self, + node: &'a PNode, + name: &str, + ) -> Result<(usize, usize), Error> { + for (mapping_idx, mapping) in self.mappings.iter().enumerate() { + for (segment_idx, segment) in mapping.segments.iter().enumerate() { + if segment.name == name { + return Ok((mapping_idx, segment_idx)); + } + } + } + + Err(Error { + line: node.value.line, + message: format!("unknown segment '{name}'"), + source: self.source_for(node), + expanded_from: self.macro_context.clone(), + global: false, + }) + } + // Change the current segment to the one referenced in `node`. fn switch_to_segment(&mut self, node: &'a PNode) -> Result<(), Error> { let name = self.fetch_quoted_first_argument(node)?; @@ -2941,28 +2992,10 @@ impl<'a> Assembler<'a> { }); } - // Find the segment being referenced and update the - // `self.current_segment` accordingly. - let mut found = false; - for (mapping_idx, mapping) in self.mappings.iter().enumerate() { - for (segment_idx, segment) in mapping.segments.iter().enumerate() { - if segment.name == name { - self.current_mapping = mapping_idx; - self.current_segment = segment_idx; - found = true; - break; - } - } - } - if !found { - return Err(Error { - line: node.value.line, - message: format!("unknown segment '{name}'"), - source: self.source_for(node), - expanded_from: self.macro_context.clone(), - global: false, - }); - } + let (midx, sidx) = self.get_mapping_segment_by_name(node, name)?; + self.current_mapping = midx; + self.current_segment = sidx; + Ok(()) } diff --git a/lib/xixanta/src/node.rs b/lib/xixanta/src/node.rs index 4f25158..649f333 100644 --- a/lib/xixanta/src/node.rs +++ b/lib/xixanta/src/node.rs @@ -276,6 +276,7 @@ pub enum CommentType { AsanStack(Range), AsanIgnore, AsanSafe, + AsanFixedSegments(Vec), } /// The PNode type. @@ -380,6 +381,7 @@ impl fmt::Display for NodeType { CommentType::AsanStack(_) => write!(f, ";; asan:stack"), CommentType::AsanIgnore => write!(f, ";; asan:ignore"), CommentType::AsanSafe => write!(f, ";; asan:safe"), + CommentType::AsanFixedSegments(_) => write!(f, ";; asan:fixed-segments"), }, } } diff --git a/lib/xixanta/src/parser.rs b/lib/xixanta/src/parser.rs index 03039f1..abcf1e8 100644 --- a/lib/xixanta/src/parser.rs +++ b/lib/xixanta/src/parser.rs @@ -264,6 +264,77 @@ impl Parser { source: self.current_source, }); } + "check:fixed-segments" | "asan:fixed-segments" => { + let mut args = vec![]; + let mut eol = false; + let end = offset; + + while !eol { + // We will assume that this is the last argument unless the + // last loop sees a comma. + eol = true; + + // Skip initial whitespaces. + for c in line.get(offset..).unwrap_or("").chars() { + if !c.is_whitespace() { + break; + } + offset += 1; + } + + // Fetch the argument and push it unless it's empty. + let mut arg = String::from(""); + for c in line.get(offset..).unwrap_or("").chars() { + if c.is_whitespace() { + offset += 1; + break; + } else if c == ',' { + break; + } + offset += 1; + + arg.push(c); + } + if !arg.is_empty() { + args.push(arg); + } + + // Skip whitespaces until a comma if available. + for c in line.get(offset..).unwrap_or("").chars() { + offset += 1; + + if c == ',' { + eol = false; + break; + } + } + } + + if args.is_empty() { + return Err(Error { + line: self.line, + global: false, + source: self.sources[self.current_source].clone(), + message: "'asan:fixed-segments' expects at least one argument".to_string(), + expanded_from: vec![], + } + .into()); + } + + self.nodes.last_mut().unwrap().push(PNode { + node_type: NodeType::Comment(CommentType::AsanFixedSegments(args)), + value: PString { + value: cmd, + line: self.line, + start, + end, + }, + left: None, + right: None, + args: None, + source: self.current_source, + }); + } &_ => {} } @@ -3843,4 +3914,63 @@ VAR3 = $200 ;; asan:reserve $100 ); assert_eq!(errors.get(4).unwrap().message, "bad 'asan:stack' range"); } + + #[test] + fn parse_fixed_segments() { + let code = r#";; asan:fixed-segments CODE +;; asan:fixed-segments CODE , FIXED +;; asan:fixed-segments TAIL, FIXED, +"#; + let mut parser = Parser::default(); + assert!( + parser + .parse(code.as_bytes(), &SourceInfo::default()) + .is_ok() + ); + + let nodes = parser.nodes(); + assert_eq!(nodes.len(), 3); + + assert_node( + nodes.first().unwrap(), + NodeType::Comment(CommentType::AsanFixedSegments(vec!["CODE".to_string()])), + code, + "asan:fixed-segments", + ); + assert_node( + nodes.get(1).unwrap(), + NodeType::Comment(CommentType::AsanFixedSegments(vec![ + "CODE".to_string(), + "FIXED".to_string(), + ])), + code, + "asan:fixed-segments", + ); + assert_node( + nodes.get(2).unwrap(), + NodeType::Comment(CommentType::AsanFixedSegments(vec![ + "TAIL".to_string(), + "FIXED".to_string(), + ])), + code, + "asan:fixed-segments", + ); + } + + #[test] + fn parse_bad_asan_fixed_segments() { + let code = ";; asan:fixed-segments"; + + let mut parser = Parser::default(); + let res = parser.parse(code.as_bytes(), &SourceInfo::default()); + + assert!(res.is_err()); + let errors = res.unwrap_err(); + assert_eq!(errors.len(), 1); + + assert_eq!( + errors.first().unwrap().message, + "'asan:fixed-segments' expects at least one argument" + ); + } } -- cgit v1.2.3