public void Link_MultipleDefinedAtomsWithTheSameName_ThrowsException() { var a = new ObjectFile(); a.Atoms.Add(new Procedure() { Name = "Proc", IsDefined = true }); var b = new ObjectFile(); b.Atoms.Add(new Procedure() { Name = "Proc", IsDefined = true }); var linker = new AtomLinker(); var message = "There are multiple atoms with called 'Proc'."; Assert.That( () => linker.Link(new[] { a, b }), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
/// <summary> /// Writes an object file to a stream. /// </summary> /// <param name="file">The object file to write.</param> /// <param name="destination">The stream the object file is written to.</param> /// <exception cref="ArgumentNullException"> /// <paramref name="file"/> or <paramref name="destination"/> is null. /// </exception> /// <exception cref="ArgumentException"> /// <paramref name="destination"/> is read only. /// </exception> public void Write(ObjectFile file, Stream destination) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (!destination.CanWrite) { throw new ArgumentException("The stream is read only.", nameof(destination)); } using (writer = new BinaryWriter(destination, Encoding.UTF8, true)) { // Write header. writer.Write(0x6D6F7461); // Ascii for "atom" backwards. writer.Write((ushort)1); writer.Write(file.IsOriginSet); writer.Write(file.Origin); of = file; foreach (dynamic atom in file) { Write(atom); } } }
public void Link_MultipleMains_ThrowsException() { var a = new ObjectFile(); a.Atoms.Add(new Procedure() { Name = "Proc1", IsMain = true }); var b = new ObjectFile(); b.Atoms.Add(new Procedure() { Name = "Proc2", IsMain = true }); var linker = new AtomLinker(); var message = "Multiple main procedures."; Assert.That( () => linker.Link(new[] { a, b }), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
public void Link_UndefinedAtoms_ThrowsException() { var a = new ObjectFile(); var b = new ObjectFile(); b.Atoms.Add(new NullTerminatedString() { Name = "A" }); var procedure = new Procedure(); procedure.IsDefined = true; procedure.Name = "Proc"; procedure.References.Add(new Reference(b.Atoms[0])); b.Atoms.Add(procedure); var linker = new AtomLinker(); var message = new StringBuilder(); message.AppendLine("Undefined atoms:"); message.AppendLine("\tA"); Assert.That( () => linker.Link(new[] { a, b }, new MemoryStream()), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message.ToString())); }
/// <summary> /// Copies the references from the object <paramref name="files"/>. /// </summary> /// <param name="files">The object files to copy references from.</param> /// <param name="combined">The object file that owns the copied references.</param> private static void CopyReferences(IEnumerable <ObjectFile> files, ObjectFile combined) { foreach (var file in files) { foreach (var procedure in file.OfType <Procedure>().Where(p => p.IsDefined)) { foreach (var reference in procedure.References) { var proc = combined.OfType <Procedure>().First(p => p.Name == procedure.Name); var referenced = combined.First(a => a.Name == reference.Atom.Name); var r = new Reference(referenced); r.Address = reference.Address; r.IsAddressInLittleEndian = reference.IsAddressInLittleEndian; r.SizeOfAddress = reference.SizeOfAddress; if (!referenced.IsGlobal) { var a = GetDefiningObjectFile(referenced.Name, files); var b = GetDefiningObjectFile(proc.Name, files); if (a != null && b != null && a != b) { throw new InvalidObjectFileException("'" + proc.Name + "' is referencing '" + referenced.Name + "' which is local to another object file."); } } proc.References.Add(r); } } } }
public void Link_2FilesWith1Atom_LinksObjectFiles() { var a = new ObjectFile(); a.Atoms.Add(new NullTerminatedString() { Content = "Abc", IsDefined = true, Name = "A" }); var b = new ObjectFile(); b.Atoms.Add(new NullTerminatedString() { Content = "Def", IsDefined = true, Name = "B" }); var linker = new AtomLinker(); var c = linker.Link(new[] { a, b }); Assert.AreEqual(2, c.Atoms.Count); Assert.AreEqual("A", ((NullTerminatedString)c.Atoms[0]).Name); Assert.AreEqual("Abc", ((NullTerminatedString)c.Atoms[0]).Content); Assert.True(((NullTerminatedString)c.Atoms[0]).IsDefined); Assert.AreEqual("B", ((NullTerminatedString)c.Atoms[1]).Name); Assert.AreEqual("Def", ((NullTerminatedString)c.Atoms[1]).Content); Assert.True(((NullTerminatedString)c.Atoms[1]).IsDefined); }
public void Link_LocalAtomInFile2ReferencedByAtomInFile1_ThrowsException() { var a = new ObjectFile(); a.Atoms.Add(new NullTerminatedString() { Name = "A" }); var procedure = new Procedure(); procedure.IsDefined = true; procedure.Name = "Proc"; procedure.References.Add(new Reference(a.Atoms[0])); a.Atoms.Add(procedure); var b = new ObjectFile(); b.Atoms.Add(new NullTerminatedString() { IsDefined = true, Content = "Abc", Name = "A" }); var linker = new AtomLinker(); var message = "'Proc' is referencing 'A' which is local to another object file."; Assert.That( () => linker.Link(new[] { a, b }), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
/// <summary> /// Resolves the references the given <paramref name="file"/> have. /// </summary> /// <param name="file">The atoms with references.</param> /// <exception cref="InvalidObjectFileException"> /// There are references with overlapping addresses. /// </exception> private void ResolveReferences(ObjectFile file) { foreach (var tuple in references) { if (file.Atoms.Count < tuple.Item2) { var message = string.Format("The atom called '{0}' has a reference to atom number {1} which doesn't exist.", tuple.Item1.Name, tuple.Item2); throw new InvalidObjectFileException(message); } var reference = new Reference(file.Atoms[(int)tuple.Item2]); reference.IsAddressInLittleEndian = tuple.Item3; reference.SizeOfAddress = tuple.Item4; reference.Address = tuple.Item5; foreach (var r in tuple.Item1.References) { if (r.IsOverlapping(reference)) { var message = string.Format("{0}'s reference to '{1}' has an overlapping address with the reference to '{2}' at {3}.", tuple.Item1.Name, r.Atom.Name, reference.Atom.Name, ToHex(r.Address)); throw new InvalidObjectFileException(message); } } tuple.Item1.References.Add(reference); } }
/// <summary> /// Reads the atoms from the <paramref name="source"/>. /// </summary> /// <param name="source">The source to read atoms from.</param> /// <returns>The atoms read from the <paramref name="source"/>.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="source"/> is null. /// </exception> /// <exception cref="ArgumentException"> /// There is no header on the current position in the <paramref name="source"/>. /// </exception> /// <exception cref="InvalidObjectFileException"> /// <paramref name="source"/> is an invalid object file. /// </exception> public ObjectFile Read(Stream source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (source.Length - source.Position <= 13) { throw new ArgumentException("There is no header on the current position.", nameof(source)); } var file = new ObjectFile(); using (reader = new BinaryReader(source)) { // This number is ascii for "atom" backwards. if (reader.ReadInt32() != 0x6D6F7461) { throw new InvalidObjectFileException("This is not an atom object file."); } if (reader.ReadUInt16() != 1) { throw new InvalidObjectFileException("This object file is not using version one."); } file.IsOriginSet = reader.ReadBoolean(); file.Origin = reader.ReadUInt64(); while (!EndOfFile) { switch (reader.ReadByte()) { case 0: file.Atoms.Add(Procedure()); break; case 1: file.Atoms.Add(NullTerminatedString()); break; case 2: file.Atoms.Add(Data()); break; default: throw new InvalidObjectFileException("Invalid atom type at " + ToHex(reader.BaseStream.Position - 1)); } } } ResolveReferences(file); return(file); }
public void Link_NoMainProcedure_ThrowsException() { var a = new ObjectFile(); var linker = new AtomLinker(); var message = "There is no main procedure."; Assert.That( () => linker.Link(new[] { a }, new MemoryStream()), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
public void Write_ProcedureWithoutReferences_ProcedureWritten() { byte[] bytes = { 0x61, 0x74, 0x6F, 0x6D, // Magic number. 0x01, 0x00, // Version 1. 0x01, // The origin is set. 0x20, 0x00, 0x00, 0x00, // The origin is set to 0x20. 0x00, 0x00, 0x00, 0x00, // Base part of the atom. 0x00, // Procedure type. 0x01, // The procedure is defined. 0x01, // The procedure is global. 0x50, 0x72, 0x6F, 0x63, 0x00, // The procedure is called 'Proc'. // Procedure part of the atom. 0x01, // This is the main procedure. 0x01, 0x00, 0x00, 0x00, // The size of the code is 1 byte. 0xAA, // The procedure code. 0x00, 0x00 // Number of references. }; var procedure = new Procedure() { IsDefined = true, IsGlobal = true, Name = "Proc", IsMain = true }; procedure.Code.Add(0xAA); using (var stream = new MemoryStream()) { var writer = new AtomWriter(); var file = new ObjectFile(); file.IsOriginSet = true; file.Origin = 0x20; file.Atoms.Add(procedure); writer.Write(file, stream); CollectionAssert.AreEqual(bytes, stream.ToArray()); } }
public void Link_ProcedureReferencingData_LinksToBinary() { var file = new ObjectFile(); var data = new Data(); file.Atoms.Add(data); data.IsDefined = true; data.Name = "Data"; data.Content.Add(0xAA); data.Content.Add(0x55); var procedure = new Procedure(); file.Atoms.Add(procedure); procedure.IsMain = true; procedure.IsDefined = true; procedure.Name = "Proc"; procedure.Code.Add(0x00); procedure.Code.Add(0x00); procedure.References.Add(new Reference(data) { IsAddressInLittleEndian = true, SizeOfAddress = 2 }); var binary = new byte[] { 0x02, 0x00, // Procedure code. 0xAA, 0x55, // The data chunk. }; using (var stream = new MemoryStream()) { var linker = new AtomLinker(); linker.Link(new[] { file }, stream); CollectionAssert.AreEqual(binary, stream.ToArray()); } }
public void Link_ProcedureReferencingProcedure_LinksToBinary() { var file = new ObjectFile(); var sub = new Procedure(); file.Atoms.Add(sub); sub.IsDefined = true; sub.Name = "Sub"; sub.Code.Add(0xAA); sub.Code.Add(0x55); var main = new Procedure(); file.Atoms.Add(main); main.IsMain = true; main.IsDefined = true; main.Name = "Proc"; main.Code.Add(0x00); main.Code.Add(0x00); main.References.Add(new Reference(sub) { IsAddressInLittleEndian = true, SizeOfAddress = 2 }); var binary = new byte[] { 0x02, 0x00, // Main procedure. 0xAA, 0x55, // Sub procedure. }; using (var stream = new MemoryStream()) { var linker = new AtomLinker(); linker.Link(new[] { file }, stream); CollectionAssert.AreEqual(binary, stream.ToArray()); } }
public void Write_Data_DataWritten() { byte[] bytes = { 0x61, 0x74, 0x6F, 0x6D, // Magic number. 0x01, 0x00, // Version 1. 0x01, // The origin is set. 0x20, 0x00, 0x00, 0x00, // The origin is set to 0x20. 0x00, 0x00, 0x00, 0x00, // Base part of the atom. 0x02, // Data type. 0x01, // The data is defined. 0x01, // The data is global. 0x44, 0x61, 0x74, 0x61, 0x00, // The data is called 'Data'. // Data part of the atom. 0x01, 0x00, 0x00, 0x00, // The size of the data block is 1 byte. 0xAA // The data block. }; var data = new Data() { IsDefined = true, IsGlobal = true, Name = "Data" }; data.Content.Add(0xAA); using (var stream = new MemoryStream()) { var writer = new AtomWriter(); var file = new ObjectFile(); file.IsOriginSet = true; file.Origin = 0x20; file.Atoms.Add(data); writer.Write(file, stream); CollectionAssert.AreEqual(bytes, stream.ToArray()); } }
public void Link_InconsistentOrigin_ThrowsException() { var a = new ObjectFile(); a.IsOriginSet = true; a.Origin = 0x10; var b = new ObjectFile(); b.IsOriginSet = true; var linker = new AtomLinker(); var message = "Inconsistent origin."; Assert.That( () => linker.Link(new[] { a, b }), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
public void Write_NullTerminatedString_StringWritten() { byte[] bytes = { 0x61, 0x74, 0x6F, 0x6D, // Magic number. 0x01, 0x00, // Version 1. 0x01, // The origin is set. 0x20, 0x00, 0x00, 0x00, // The origin is set to 0x20. 0x00, 0x00, 0x00, 0x00, // Base part of the atom. 0x01, // String type. 0x01, // The string is defined. 0x01, // The string is global. 0x54, 0x78, 0x74, 0x00, // The string is called 'Txt'. // String part of the atom. 0x41, 0x62, 0x63, 0x00 // The string is 'Abc'. }; var s = new NullTerminatedString() { IsDefined = true, IsGlobal = true, Name = "Txt", Content = "Abc" }; using (var stream = new MemoryStream()) { var writer = new AtomWriter(); var file = new ObjectFile(); file.IsOriginSet = true; file.Origin = 0x20; file.Atoms.Add(s); writer.Write(file, stream); CollectionAssert.AreEqual(bytes, stream.ToArray()); } }
public void Link_ProcedureReferencingNullTerminatedString_LinksToBinary() { var file = new ObjectFile(); file.Atoms.Add(new NullTerminatedString() { IsDefined = true, Content = "Abc", Name = "A" }); var procedure = new Procedure(); file.Atoms.Add(procedure); procedure.IsMain = true; procedure.IsDefined = true; procedure.Name = "Proc"; procedure.Code.Add(0x00); procedure.Code.Add(0x00); procedure.References.Add(new Reference(file.Atoms[0]) { IsAddressInLittleEndian = true, SizeOfAddress = 2 }); var binary = new byte[] { 0x02, 0x00, // Procedure code. 0x41, 0x62, 0x63, 0x00 // The string 'Abc'. }; using (var stream = new MemoryStream()) { var linker = new AtomLinker(); linker.Link(new[] { file }, stream); CollectionAssert.AreEqual(binary, stream.ToArray()); } }
public void Link_GlobalAtomInFile1ReferencedByAtomInFile2_LinksObjectFiles() { var a = new ObjectFile(); a.Atoms.Add(new NullTerminatedString() { IsDefined = true, IsGlobal = true, Content = "Abc", Name = "A" }); var b = new ObjectFile(); b.Atoms.Add(new NullTerminatedString() { Name = "A" }); var procedure = new Procedure(); procedure.IsDefined = true; procedure.Name = "Proc"; procedure.References.Add(new Reference(b.Atoms[0])); b.Atoms.Add(procedure); var linker = new AtomLinker(); var c = linker.Link(new[] { a, b }); Assert.AreEqual(2, c.Atoms.Count); Assert.AreEqual("A", ((NullTerminatedString)c.Atoms[0]).Name); Assert.AreEqual("Abc", ((NullTerminatedString)c.Atoms[0]).Content); Assert.True(((NullTerminatedString)c.Atoms[0]).IsDefined); Assert.AreEqual("Proc", ((Procedure)c.Atoms[1]).Name); Assert.AreSame(((Procedure)c.Atoms[1]).References[0].Atom, c.Atoms[0]); Assert.True(((Procedure)c.Atoms[1]).IsDefined); }
public void Write_NoAtoms_HeaderWritten() { byte[] bytes = { 0x61, 0x74, 0x6F, 0x6D, // Magic number. 0x01, 0x00, // Version 1. 0x01, // The origin is set. 0x20, 0x00, 0x00, 0x00, // The origin is set to 0x20. 0x00, 0x00, 0x00, 0x00, }; using (var stream = new MemoryStream()) { var writer = new AtomWriter(); var file = new ObjectFile(); file.IsOriginSet = true; file.Origin = 0x20; writer.Write(file, stream); CollectionAssert.AreEqual(bytes, stream.ToArray()); } }
public void Link_ObjectFileWithMainAndUnreferencedAtoms_OnlyMainIsLinked() { var file = new ObjectFile(); var sub = new Procedure(); file.Atoms.Add(sub); sub.IsDefined = true; sub.Name = "Sub"; sub.Code.Add(0xAA); sub.Code.Add(0x55); var main = new Procedure(); file.Atoms.Add(main); main.IsMain = true; main.IsDefined = true; main.Name = "Proc"; main.Code.Add(0x00); main.Code.Add(0x00); var binary = new byte[] { 0x00, 0x00, // Main procedure. }; using (var stream = new MemoryStream()) { var linker = new AtomLinker(); linker.Link(new[] { file }, stream); CollectionAssert.AreEqual(binary, stream.ToArray()); } }
public void Link_MultipleUndefinedDifferentlyTypedAtomsWithTheSameName_ThrowsException() { var a = new ObjectFile(); a.Atoms.Add(new Procedure() { Name = "Proc" }); var b = new ObjectFile(); b.Atoms.Add(new Data() { Name = "Proc" }); var linker = new AtomLinker(); var message = "'Proc' and 'Proc' is not of the same type."; Assert.That( () => linker.Link(new[] { a, b }), Throws.TypeOf <InvalidObjectFileException>().With.Message.EqualTo(message)); }
public void Write_ProcedureWithReference_ProcedureRead() { byte[] bytes = { 0x61, 0x74, 0x6F, 0x6D, // Magic number. 0x01, 0x00, // Version 1. 0x01, // The origin is set. 0x20, 0x00, 0x00, 0x00, // The origin is set to 0x20. 0x00, 0x00, 0x00, 0x00, // Base part of the atom. 0x00, // Procedure type. 0x01, // The procedure is defined. 0x01, // The procedure is global. 0x50, 0x72, 0x6F, 0x63, 0x00, // The procedure is called 'Proc'. // Procedure part of the atom. 0x01, // This is the main procedure. 0x04, 0x00, 0x00, 0x00, // The size of the code is 4 bytes. 0x00, 0x00, 0x00, 0x00, // The procedure code. 0x01, 0x00, // Number of references. // Reference. 0x01, 0x00, 0x00, 0x00, // It is atom number 1 that is being referenced (index based). 0x01, // The address is in little endian. 0x04, // The size of the address is 4 bytes. 0x00, 0x00, 0x00, 0x00, // The address to relocate. // Null terminated string. 0x01, // Null terminated string type. 0x01, // The string is defined. 0x00, // The string is not global. 0x53, 0x00, // The string is called 'S'. // String part 0x54, 0x78, 0x74, 0x00 // The string is 'Txt'. }; var s = new NullTerminatedString() { IsDefined = true, IsGlobal = false, Name = "S", Content = "Txt" }; var procedure = new Procedure() { IsDefined = true, IsGlobal = true, Name = "Proc", IsMain = true }; procedure.Code.Add(0x00); procedure.Code.Add(0x00); procedure.Code.Add(0x00); procedure.Code.Add(0x00); procedure.References.Add(new Reference(s) { IsAddressInLittleEndian = true, SizeOfAddress = 4, Address = 0x00 }); using (var stream = new MemoryStream()) { var writer = new AtomWriter(); var file = new ObjectFile(); file.IsOriginSet = true; file.Origin = 0x20; file.Atoms.Add(procedure); file.Atoms.Add(s); writer.Write(file, stream); CollectionAssert.AreEqual(bytes, stream.ToArray()); } }
/// <summary> /// Combines object files into a single one. /// </summary> /// <param name="files">The object files to combine.</param> /// <returns>The combined object file.</returns> /// <exception cref="ArgumentNullException"> /// <paramref name="files"/> is null. /// </exception> /// <exception cref="InvalidObjectFileException"> /// The combined object file is invalid. /// </exception> public ObjectFile Link(IEnumerable <ObjectFile> files) { if (files == null) { throw new ArgumentNullException(nameof(files)); } var combined = new ObjectFile(); foreach (var file in files) { if (combined.Origin != file.Origin) { if (combined.IsOriginSet) { throw new InvalidObjectFileException("Inconsistent origin."); } combined.IsOriginSet = true; combined.Origin = file.Origin; } foreach (var atom in file) { var procedure = atom as Procedure; if (procedure != null) { if (combined.OfType <Procedure>().Any(p => p.IsMain) && procedure.IsMain) { throw new InvalidObjectFileException("Multiple main procedures."); } } var duplicate = combined.FirstOrDefault(a => a.Name == atom.Name); if (duplicate != null) { if (duplicate.IsDefined && atom.IsDefined) { throw new InvalidObjectFileException("There are multiple atoms with called '" + atom.Name + "'."); } if (duplicate.GetType() != atom.GetType()) { throw new InvalidObjectFileException("'" + duplicate.Name + "' and '" + atom.Name + "' is not of the same type."); } if (!duplicate.IsDefined && !atom.IsDefined) { continue; } if (duplicate.IsDefined && !atom.IsDefined) { continue; } combined.Atoms.Remove(duplicate); } combined.Atoms.Add(Copy(atom)); } } CopyReferences(files, combined); return(combined); }