aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/nasm/README.md16
-rw-r--r--crates/nasm/src/main.rs19
-rw-r--r--lib/xixanta/src/assembler.rs86
3 files changed, 116 insertions, 5 deletions
diff --git a/crates/nasm/README.md b/crates/nasm/README.md
index 2675bfb..750e795 100644
--- a/crates/nasm/README.md
+++ b/crates/nasm/README.md
@@ -246,6 +246,22 @@ fit in a byte. Hence, you could have a code like follows:
If you compile the code with `-D PAL=1`, then the first branch will be taken
instead of the second one.
+## The .nasm/ directory
+
+When you enable the `--write-info` flag, some files will be written into a
+hidden `.nasm/` directory. This directory will contain debug information that
+can be later used by other tools. That being said, some of these files can also
+be useful to programmers. They are as follows:
+
+- `segments.txt`: summary of the segments being used and how much they have been
+ filled. Note that you need to also pass `--stats` to get this file.
+- `memory.txt`: list of variables being used, expressed as ranges to account for
+ reservation via the `asan:reserve` special comment. Note that you need to also
+ pass `--asan` to get this file.
+- `addresses.txt`: list of addresses known by the assembler, expressed as ranges
+ to account for blocks of code like proc's. You don't need any other flag for
+ this file.
+
## Unused code
This assembler will also issue a warning whenever it finds unreferenced
diff --git a/crates/nasm/src/main.rs b/crates/nasm/src/main.rs
index 55a8f21..010e7d5 100644
--- a/crates/nasm/src/main.rs
+++ b/crates/nasm/src/main.rs
@@ -270,6 +270,20 @@ fn print_memory_summary(mut output: Box<dyn Write>, memory: &MemoryResult, has_w
}
}
+// Save the 'addresses' list into the `<source>/.nasm/addresses.txt` file.
+fn save_addresses(source: &SourceInfo, mut addresses: Vec<xixanta::assembler::Range>) {
+ let Ok(mut file) = File::create(get_directory_from_source(source).join("addresses.txt")) else {
+ return die("could not write memory.txt file".to_string());
+ };
+
+ addresses.sort_by_key(|a| a.range.start);
+ for a in addresses {
+ if let Err(e) = writeln!(file, "{},{:04X},{:04X}", a.name, a.range.start, a.range.end) {
+ return die(format!("could not write addresses.txt file: {e}"));
+ }
+ }
+}
+
// Print to the `output` stream the statistics that can be gathered from the
// given `mappings`.
fn print_segments_stats(mut output: Box<dyn Write>, mappings: &[Mapping]) {
@@ -463,4 +477,9 @@ fn main() {
print_memory_summary(Box::new(io::stdout()), &memory, has_working_ram);
}
}
+
+ // Print addresses.
+ if args.info {
+ save_addresses(&source, res.addresses);
+ }
}
diff --git a/lib/xixanta/src/assembler.rs b/lib/xixanta/src/assembler.rs
index 81ac9ee..959840d 100644
--- a/lib/xixanta/src/assembler.rs
+++ b/lib/xixanta/src/assembler.rs
@@ -187,6 +187,18 @@ struct Assembler<'a> {
// List of mapping/segment indeces which the programmer asked to be
// considered safe.
safe_segments: Vec<(usize, usize)>,
+
+ // Name of the last proc that was encountered while traversing the parsed
+ // nodes.
+ last_proc: String,
+
+ // Target address that was last saved in push_bundle().
+ last_address: Option<usize>,
+
+ // Map that identifies for each given string identifying a piece of code
+ // (e.g. a proc's name), the address which can be used as the 'end' for a
+ // Range object.
+ address_ends: HashMap<String, usize>,
}
/// The result to be given at the end of `assembler::assemble` and
@@ -232,6 +244,14 @@ pub struct AssemblerResult {
/// The resaulting memory analysis from the address sanitizer. Ignore this
/// if you did not assemble the source with the address sanitizer enabled.
pub memory: MemoryResult,
+
+ /// The addresses that have been evaluated. Note that some addresses will be
+ /// proper ranges (i.e. proc's), while others will only be a value (i.e.
+ /// labels). This is done in purpose as there's no reliable way to guess
+ /// whether a label was actually a subroutine or not. Hence, we don't even
+ /// try and encourage programmers to use .proc's when necessary (see
+ /// style.nes).
+ pub addresses: Vec<Range>,
}
/// Read the contents from the `reader` as a source file and produce a list of
@@ -264,6 +284,7 @@ pub fn assemble(
mappings: vec![],
accessing_working_ram: false,
memory: MemoryResult::default(),
+ addresses: vec![],
};
}
};
@@ -291,6 +312,7 @@ pub fn assemble_with_mapping(
let mut asm = Assembler::new(mapping);
let mut memory = MemoryResult::default();
+ let mut addresses = vec![];
asm.asan_enabled = asan;
asm.allow_unused = allow_unused;
@@ -305,6 +327,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -322,6 +345,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
}
@@ -336,6 +360,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -372,6 +397,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -385,12 +411,13 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
// Before filling in the holes with fill(), let's check what we have
// processed so far.
- if let Err(errors) = asm.check(&mut memory) {
+ if let Err(errors) = asm.check(&mut memory, &mut addresses) {
return AssemblerResult {
bundles: vec![],
errors,
@@ -398,6 +425,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
};
}
@@ -416,6 +444,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
},
Err(errors) => AssemblerResult {
bundles: vec![],
@@ -424,6 +453,7 @@ pub fn assemble_with_mapping(
mappings: asm.mappings,
accessing_working_ram: asm.accessing_working_ram,
memory,
+ addresses,
},
}
}
@@ -457,6 +487,9 @@ impl<'a> Assembler<'a> {
pending_defines: vec![],
objects_visited: vec![],
safe_segments: vec![],
+ last_proc: String::new(),
+ last_address: None,
+ address_ends: HashMap::new(),
}
}
@@ -915,6 +948,8 @@ impl<'a> Assembler<'a> {
// then open up its inner context.
NodeType::Control(ControlType::StartProc) => {
let proc_name = &node.left.as_ref().unwrap();
+ self.last_proc = proc_name.value.value.clone();
+
if let Err(e) = self.apply_segment_offset_to_label(proc_name, ObjectType::Proc)
{
errors.push(e);
@@ -985,6 +1020,23 @@ impl<'a> Assembler<'a> {
ControlType::StartRepeat => {
self.evaluate_repeat_statement(node)?;
}
+ ControlType::EndProc => {
+ // Save the last visited proc as the index for the
+ // 'address_ends' map. Note that we need to have it
+ // here because after the previous
+ // evaluate_control_statement() call the context
+ // will be the right one.
+
+ let context_name = self.context.name();
+ let full_name = if context_name == GLOBAL_CONTEXT {
+ self.last_proc.clone()
+ } else {
+ format!("{context_name}::{}", self.last_proc)
+ };
+ if let Some(last_address) = self.last_address {
+ self.address_ends.insert(full_name, last_address);
+ }
+ }
ControlType::StartScope
// If this is the start of a .scope statement, then go
// inside of its body too if it exists (note that its
@@ -1275,8 +1327,12 @@ impl<'a> Assembler<'a> {
}
// Perform different checks on the bundles we have produced so far, and
- // update the 'memory' if possible (i.e. ASAN is enabled).
- fn check(&mut self, memory: &mut MemoryResult) -> Result<(), Vec<Error>> {
+ // update the 'memory' and the 'addresses' if possible.
+ fn check(
+ &mut self,
+ memory: &mut MemoryResult,
+ addresses: &mut Vec<Range>,
+ ) -> Result<(), Vec<Error>> {
let mut errors = vec![];
for (context_name, map) in self.context.map.iter() {
@@ -1370,8 +1426,21 @@ impl<'a> Assembler<'a> {
// addresses.
match bundle.object_type {
ObjectType::Proc | ObjectType::Address => {
- // TODO
- println!("{full_name}");
+ // Using unwrap() is safe as it only returns an error on
+ // badly formatted addresses. This will not happen at
+ // this point.
+ let resolved = self.context.resolve_label(&self.mappings, &bundle).unwrap();
+
+ let val = resolved.bundle.value() as usize;
+ let end = match self.address_ends.get(&full_name) {
+ Some(v) => v,
+ None => &val,
+ };
+ let range = Range {
+ range: (val..*end),
+ name: full_name,
+ };
+ addresses.push(range);
}
ObjectType::Value
// We have a value at hand. If the address sanitizer is
@@ -1696,6 +1765,13 @@ impl<'a> Assembler<'a> {
current.offset += bundle.size as usize;
current.segments[self.current_segment].offset += bundle.size as usize;
+ // Save this bundle as the last address being used. Note that the
+ // address being pointed at is _after_ consuming the bundle (i.e. by
+ // adding 'bundle.size'). This is because in the end this 'last_address'
+ // is going to be used by the Range struct as its 'end' member. Since
+ // this is going to be inclusive, we need to point at the "next" bundle.
+ self.last_address = Some(bundle.address + bundle.size as usize);
+
// Mark this bundle as reference-safe, so the checker afterwards doesn't
// complain on different checks.
bundle.safe = self.asan_next_safe;