aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiquel Sabaté Solà <mssola@mssola.com>2026-10-09 21:31:33 +0200
committerMiquel Sabaté Solà <mssola@mssola.com>2026-10-09 21:31:33 +0200
commitaa5174fc055dc66e78a8aa7030c018e43cf588a1 (patch)
tree63517cb82d0951648e246c84cd1b74df0c42ed88
parentdbd5a40d7db5f9dac1131928aff284cec04b61b1 (diff)
downloadtools.nes-aa5174fc055dc66e78a8aa7030c018e43cf588a1.tar.gz
tools.nes-aa5174fc055dc66e78a8aa7030c018e43cf588a1.zip
xixanta: Add an hint for the ending of data labels
Some raw labels may point at a bunch of data laid out with the .byte family of statements. Add hints for each of these labels so we also have a range for them. This way, tools like 'vnf' might be able to detect bad accesses and these sets of data. Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
-rw-r--r--lib/xixanta/src/assembler.rs102
1 files changed, 92 insertions, 10 deletions
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 6006234..db53e77 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -199,6 +199,14 @@ struct Assembler<'a> {
// (e.g. a proc's name), the address which can be used as the 'end' for a
// Range object.
address_ends: HashMap<String, usize>,
+
+ // Map that identifies the tentative end for a set of raw data indexed at a
+ // given simple label. This is different than 'address_ends' as the computed
+ // 'end' of the range is not guaranteed (i.e. there's no closing
+ // '.endproc'), and the given integer could not be computed via addresses,
+ // but as a difference of "offsets". Thus, the value for each key is
+ // actually the distance in bytes from the start to the end.
+ hinted_ends: HashMap<String, usize>,
}
/// The result to be given at the end of `assembler::assemble` and
@@ -490,6 +498,7 @@ impl<'a> Assembler<'a> {
last_proc: String::new(),
last_address: None,
address_ends: HashMap::new(),
+ hinted_ends: HashMap::new(),
}
}
@@ -900,14 +909,15 @@ impl<'a> Assembler<'a> {
// 'node' and taking 'object_type' as its type, unless the node value is
// empty (i.e. anonymous label). In either case, the computed label will be
// pushed into the context's list of known labels with the current segment
- // offset.
+ // offset. On success, it returns the offset that has been applied.
fn apply_segment_offset_to_label(
&mut self,
node: &PNode,
object_type: ObjectType,
- ) -> Result<(), Error> {
+ ) -> Result<usize, Error> {
let segment = &self.mappings[self.current_mapping].segments[self.current_segment];
- let value = segment.offset.to_le_bytes();
+ let offset = segment.offset;
+ let value = offset.to_le_bytes();
let object = Object {
bundle: Bundle {
bytes: [value[0], value[1], value[2]],
@@ -945,13 +955,19 @@ impl<'a> Assembler<'a> {
}
self.context.add_label(&object);
- Ok(())
+ Ok(offset)
}
fn bundle(&mut self, nodes: &'a [PNode]) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
let mut next_ignore = false;
+ // Variables for hinting at the end of data that is being indexed by a
+ // raw 'ObjectType::Address'.
+ let mut byte_seen = false;
+ let mut offset: Option<usize> = None;
+ let mut last_label = String::from("");
+
self.stage = Stage::Bundling;
for node in nodes {
@@ -959,12 +975,27 @@ impl<'a> Assembler<'a> {
match &node.node_type {
// Initialize the label to the offset address of the current
- // segment. Note that this is only the offset from the beginning
- // of the offset, the effective address will only be available
- // after calling `Context::get_variable`
+ // segment. Note that this is only from the beginning of the
+ // offset, the effective address will only be available after
+ // calling `Context::get_variable`
NodeType::Label(_) => {
- if let Err(e) = self.apply_segment_offset_to_label(node, ObjectType::Address) {
- errors.push(e);
+ match self.apply_segment_offset_to_label(node, ObjectType::Address) {
+ Ok(o) => {
+ // On success, grab the offset and the full name for
+ // this label. This will be used on the hinting of
+ // the end of the data that this address might be
+ // pointing at.
+ let context_name = self.context.name();
+ let name = node.value.value.clone();
+ last_label = if context_name == GLOBAL_CONTEXT {
+ name.clone()
+ } else {
+ format!("{context_name}::{}", name)
+ };
+ offset = Some(o);
+ byte_seen = true;
+ }
+ Err(e) => errors.push(e),
}
}
// Same as with labels but with the addition that ".proc"
@@ -1040,6 +1071,12 @@ impl<'a> Assembler<'a> {
// On control statements which modify the context, there are
// some further evaluating to do.
match control_type {
+ ControlType::Byte => {
+ byte_seen = true;
+ }
+ ControlType::Addr | ControlType::Word | ControlType::BigEndianWord => {
+ byte_seen = true;
+ }
ControlType::Echo(t) => self.evaluate_echo(node, t)?,
ControlType::StartRepeat => {
self.evaluate_repeat_statement(node)?;
@@ -1110,6 +1147,42 @@ impl<'a> Assembler<'a> {
_ => {}
}
+ // If a ".byte/.word"-like statement has been found, do nothing with
+ // the offset detection and just turn it off so the next iteration
+ // might need to set it again in case there are more
+ // ".byte/.word"-like statements.
+ if byte_seen {
+ byte_seen = false;
+ } else if let Some(off) = offset {
+ // A ".byte/.word"-like statement has not been found, and we had
+ // a previous raw label. Hence, we are at the scenario in which
+ // we hint to a tentative "end" offset for a bunch of indexed
+ // data.
+
+ // The 'hinted_ends' structure only wants an offset delta
+ // between the start and the end of the data. Thus, subtract the
+ // current segment's offset with the 'off' start one. There are
+ // two things to note:
+ //
+ // 1. If this is the first thing right after the label, then
+ // the offset delta might just be zero. Skip this case.
+ // 2. Unless this is the last statement, we are at the
+ // "next" instruction. Hence, subtract its size to this
+ // delta as well.
+ let segment = &self.mappings[self.current_mapping].segments[self.current_segment];
+ if segment.offset > off {
+ let mut res = segment.offset - off;
+ if let Some(bundle) = segment.bundles.last() {
+ res -= bundle.size as usize;
+ }
+ self.hinted_ends.insert(last_label, res);
+ }
+
+ // And reset the variables involved in this mechanism.
+ offset = None;
+ last_label = String::from("");
+ }
+
// Keep 'self.asan_next_safe' to false if possible.
if !next_safe {
self.asan_next_safe = false;
@@ -1480,7 +1553,16 @@ impl<'a> Assembler<'a> {
// push the address.
let end = match self.address_ends.get(&full_name) {
Some(v) => v,
- None => &val,
+ None => {
+ // If this is not a clearly defined block of
+ // code, it might be raw data indexed by a
+ // label. Try to fetch it from the 'hinted_ends'
+ // structure, which holds just an offset.
+ &match self.hinted_ends.get(&full_name) {
+ Some(offset) => val + offset,
+ None => val,
+ }
+ },
};
let range = Range {
range: (val..*end),