1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Collections.Generic; 9 using System.Globalization; 10 using System.IO; 11 using System.Linq; 12 using Antmicro.Renode.Exceptions; 13 using Antmicro.Renode.Logging; 14 using Antmicro.Renode.Peripherals; 15 using Antmicro.Renode.Peripherals.Bus; 16 using Antmicro.Renode.Peripherals.CPU; 17 using Antmicro.Renode.Utilities; 18 using ELFSharp.ELF.Segments; 19 20 namespace Antmicro.Renode.Core.Extensions 21 { 22 public static class FileLoaderExtensions 23 { LoadBinary(this ICanLoadFiles loader, ReadFilePath fileName, ulong loadPoint, ICPU cpu = null, long offset = 0)24 public static void LoadBinary(this ICanLoadFiles loader, ReadFilePath fileName, ulong loadPoint, ICPU cpu = null, long offset = 0) 25 { 26 const int bufferSize = 100 * 1024; 27 List<FileChunk> chunks = new List<FileChunk>(); 28 29 Logger.LogAs(loader, LogLevel.Debug, "Loading binary file {0}.", fileName); 30 try 31 { 32 using(var reader = new FileStream(fileName, FileMode.Open, FileAccess.Read)) 33 { 34 reader.Seek(offset, SeekOrigin.Current); 35 36 var buffer = new byte[bufferSize]; 37 var bytesCount = reader.Read(buffer, 0, buffer.Length); 38 var addr = loadPoint; 39 40 while(bytesCount > 0) 41 { 42 chunks.Add(new FileChunk() { Data = buffer.Take(bytesCount), OffsetToLoad = addr }); 43 addr += (ulong)bytesCount; 44 buffer = new byte[bufferSize]; 45 bytesCount = reader.Read(buffer, 0, buffer.Length); 46 } 47 } 48 } 49 catch(IOException e) 50 { 51 throw new RecoverableException(string.Format("Exception while loading file {0}: {1}", fileName, e.Message)); 52 } 53 54 chunks = SortAndJoinConsecutiveFileChunks(chunks); 55 loader.LoadFileChunks(fileName, chunks, cpu); 56 } 57 LoadHEX(this ICanLoadFiles loader, ReadFilePath fileName, IInitableCPU cpu = null)58 public static void LoadHEX(this ICanLoadFiles loader, ReadFilePath fileName, IInitableCPU cpu = null) 59 { 60 string line; 61 int lineNum = 1; 62 ulong extendedTargetAddress = 0; 63 ulong extendedSegmentAddress = 0; 64 bool endOfFileReached = false; 65 List<FileChunk> chunks = new List<FileChunk>(); 66 67 Logger.LogAs(loader, LogLevel.Debug, "Loading HEX file {0}.", fileName); 68 try 69 { 70 using(var file = new System.IO.StreamReader(fileName)) 71 { 72 while((line = file.ReadLine()) != null) 73 { 74 if(endOfFileReached) 75 { 76 throw new RecoverableException($"Unexpected data after the end of file marker at line #{lineNum}"); 77 } 78 79 if(line.Length < 11) 80 { 81 throw new RecoverableException($"Line is too short error at line #{lineNum}."); 82 } 83 if(line[0] != ':' 84 || !int.TryParse(line.Substring(1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var length) 85 || !ulong.TryParse(line.Substring(3, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) 86 || !byte.TryParse(line.Substring(7, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var type)) 87 { 88 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse header"); 89 } 90 91 // this does not include the final CRC 92 if(line.Length < 9 + length * 2) 93 { 94 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Line too short"); 95 } 96 97 switch((HexRecordType)type) 98 { 99 case HexRecordType.Data: 100 var targetAddr = (extendedTargetAddress << 16) | (extendedSegmentAddress << 4) | address; 101 var pos = 9; 102 var buffer = new byte[length]; 103 for(var i = 0; i < length; i++, pos += 2) 104 { 105 if(!byte.TryParse(line.Substring(pos, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out buffer[i])) 106 { 107 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse bytes"); 108 } 109 } 110 chunks.Add(new FileChunk() { Data = buffer, OffsetToLoad = targetAddr }); 111 break; 112 113 case HexRecordType.ExtendedLinearAddress: 114 if(!ulong.TryParse(line.Substring(9, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out extendedTargetAddress)) 115 { 116 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse extended linear address"); 117 } 118 break; 119 120 case HexRecordType.StartLinearAddress: 121 if(!ulong.TryParse(line.Substring(9, 8), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var startingAddress)) 122 { 123 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse starting address"); 124 } 125 126 if(cpu != null) 127 { 128 cpu.PC = startingAddress; 129 } 130 break; 131 132 case HexRecordType.ExtendedSegmentAddress: 133 if(!ulong.TryParse(line.Substring(9, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out extendedSegmentAddress)) 134 { 135 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse extended segment address"); 136 } 137 break; 138 139 case HexRecordType.StartSegmentAddress: 140 if(!ulong.TryParse(line.Substring(9, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var startingSegment) 141 || !ulong.TryParse(line.Substring(13, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var startingSegmentAddress)) 142 { 143 throw new RecoverableException($"Parsing error at line #{lineNum}: {line}. Could not parse starting segment/address"); 144 } 145 146 if(cpu != null) 147 { 148 cpu.PC = (startingSegment << 4) + startingSegmentAddress; 149 } 150 break; 151 152 case HexRecordType.EndOfFile: 153 endOfFileReached = true; 154 break; 155 156 default: 157 break; 158 } 159 lineNum++; 160 } 161 } 162 } 163 catch(IOException e) 164 { 165 throw new RecoverableException($"Exception while loading file {fileName}: {(e.Message)}"); 166 } 167 168 chunks = SortAndJoinConsecutiveFileChunks(chunks); 169 loader.LoadFileChunks(fileName, chunks, cpu); 170 } 171 LoadSRecord(this ICanLoadFiles loader, ReadFilePath fileName, IInitableCPU cpu = null)172 public static void LoadSRecord(this ICanLoadFiles loader, ReadFilePath fileName, IInitableCPU cpu = null) 173 { 174 SRecPurpose type; 175 ulong address; 176 string line = ""; 177 int lineNum = 1; 178 bool endOfFileReached = false; 179 List<FileChunk> chunks = new List<FileChunk>(); 180 Range? currentSegmentInfo = null; 181 182 Logger.LogAs(loader, LogLevel.Debug, "Loading S-record file {0}.", fileName); 183 try 184 { 185 using(var file = new System.IO.StreamReader(fileName)) 186 { 187 while((line = file.ReadLine()) != null) 188 { 189 if(endOfFileReached) 190 { 191 throw new RecoverableException($"Unexpected data after the end of file marker at line #{lineNum}"); 192 } 193 194 // an S-record consist of: 195 // * 'S' character followed by a digit character 0-9 denoting type of the record, 196 // * hexstring byte value of bytes count, e.i. number of bytes that follow this byte, 197 // * hexstring bytes representing address, number of them depends on the record type, 198 // * hexstring bytes representing data, number of them depends on bytes count, 199 // * hexstring byte value of checksum, 200 // records are seperated by end of line that can differ between operating systems. 201 202 // ensure that line contains bytes count 203 if(line.Length < SRecAddressStart) 204 { 205 throw new RecoverableException($"Line is too short error at line #{lineNum}:\n\"{line}\""); 206 } 207 208 if(line[SRecStartOfRecordIndex] != SRecStartOfRecord) 209 { 210 throw new RecoverableException($"Invalid Start-of-Record at line #{lineNum}:\n\"{line}\""); 211 } 212 213 // number of address, data and checksum bytes 214 var bytesCount = Convert.ToUInt16(line.Substring(SRecBytesCountStart, SRecBytesCountLength), 16); 215 216 if(line.Length != SRecAddressStart + bytesCount * 2) 217 { 218 throw new RecoverableException($"Line length does not match bytes count error at line #{lineNum}:\n\"{line}\""); 219 } 220 221 var addressLength = 4; 222 switch((SRecType)line[SRecTypeIndex]) 223 { 224 case SRecType.Header: 225 type = SRecPurpose.Header; 226 break; 227 case SRecType.Data16BitAddress: 228 type = SRecPurpose.Data; 229 break; 230 case SRecType.Data24BitAddress: 231 addressLength = 6; 232 type = SRecPurpose.Data; 233 break; 234 case SRecType.Data32BitAddress: 235 addressLength = 8; 236 type = SRecPurpose.Data; 237 break; 238 case SRecType.Reserved: 239 throw new RecoverableException($"Reserved record type at line #{lineNum}:\n\"{line}\""); 240 case SRecType.Count16Bit: 241 type = SRecPurpose.Count; 242 break; 243 case SRecType.Count24Bit: 244 addressLength = 6; 245 type = SRecPurpose.Count; 246 break; 247 case SRecType.Termination32BitAddress: 248 addressLength = 8; 249 type = SRecPurpose.Termination; 250 break; 251 case SRecType.Termination24BitAddress: 252 addressLength = 6; 253 type = SRecPurpose.Termination; 254 break; 255 case SRecType.Termination16BitAddress: 256 type = SRecPurpose.Termination; 257 break; 258 default: 259 throw new RecoverableException($"Invalid record type at line #{lineNum}:\n\"{line}\""); 260 } 261 262 // bytes count needs to allow for at least address and checksum bytes 263 if(bytesCount * 2 < addressLength + SRecChecksumLength) 264 { 265 throw new RecoverableException($"Bytes count is too small error at line #{lineNum}:\n\"{line}\""); 266 } 267 268 var addressString = line.Substring(SRecAddressStart, addressLength); 269 address = Convert.ToUInt32(addressString, 16); 270 271 var bufferLength = bytesCount * 2 + SRecBytesCountLength - SRecChecksumLength; 272 var checksumStart = SRecAddressStart + bytesCount * 2 - SRecChecksumLength; 273 274 var buffer = Misc.HexStringToByteArray(line.Substring(SRecBytesCountStart, bufferLength)); 275 var checksum = Convert.ToByte(line.Substring(checksumStart, SRecChecksumLength), 16); 276 277 // checksum is 0xFF minus a sum of bytes count, address and data bytes 278 var calculatedChecksum = 0xFF - buffer.Aggregate((byte)0x0, (a, b) => (byte)(a + b)); 279 if(calculatedChecksum != checksum) 280 { 281 throw new RecoverableException($"Checksum error (calculated: 0x{calculatedChecksum:X02}, given: 0x{checksum:X02}) at line #{lineNum}:\n\"{line}\""); 282 } 283 284 var data = buffer.Skip((addressLength + SRecBytesCountLength) / 2); 285 var dataLength = (ulong)(bytesCount - (addressLength + SRecChecksumLength) / 2); 286 287 switch(type) 288 { 289 case SRecPurpose.Header: 290 if(address != 0) 291 { 292 throw new RecoverableException($"Invalid Header record at line #{lineNum}:\n\"{line}\""); 293 } 294 break; 295 case SRecPurpose.Data: 296 if(!currentSegmentInfo.HasValue) 297 { 298 currentSegmentInfo = address.By(dataLength); 299 } 300 else if(currentSegmentInfo.Value.EndAddress + 1 == address) 301 { 302 currentSegmentInfo = currentSegmentInfo.Value.StartAddress.By(currentSegmentInfo.Value.Size + dataLength); 303 } 304 else 305 { 306 currentSegmentInfo = address.By(dataLength); 307 } 308 chunks.Add(new FileChunk() { Data = data.ToArray(), OffsetToLoad = address }); 309 break; 310 case SRecPurpose.Count: 311 if(dataLength != 0) 312 { 313 throw new RecoverableException($"Unexpected data in a count record error at line #{lineNum}:\n\"{line}\""); 314 } 315 if(chunks.Count != (int)address) 316 { 317 throw new RecoverableException($"Data record count mismatch error (calculated: {chunks.Count}, given: {address}) at line #{lineNum}:\n\"{line}\""); 318 } 319 break; 320 case SRecPurpose.Termination: 321 if(dataLength != 0) 322 { 323 throw new RecoverableException($"Unexpected data in a termination record error at line #{lineNum}:\n\"{line}\""); 324 } 325 if(cpu != null) 326 { 327 cpu.Log(LogLevel.Info, "Setting PC value to 0x{0:X}", address); 328 cpu.PC = address; 329 } 330 else if(loader is IBusController bus) 331 { 332 foreach(var core in bus.GetCPUs()) 333 { 334 cpu.Log(LogLevel.Info, "Setting PC value to 0x{0:X}", address); 335 core.PC = address; 336 } 337 } 338 else 339 { 340 Logger.Log(LogLevel.Warning, "S-record loader: Found start addres: 0x{0:X}, but no cpu is selected to set it for", address); 341 } 342 break; 343 default: 344 throw new Exception("Unreachable"); 345 } 346 347 lineNum++; 348 } 349 } 350 } 351 catch(IOException e) 352 { 353 throw new RecoverableException($"Exception while loading file {fileName}: {(e.Message)}"); 354 } 355 catch(FormatException e) 356 { 357 throw new RecoverableException($"Exception while parsing line #{lineNum}:\n\"{line}\"", e); 358 } 359 360 if(lineNum == 1) 361 { 362 Logger.Log(LogLevel.Warning, "S-record loader: Attempted to load empty file {0}", fileName); 363 return; 364 } 365 366 chunks = SortAndJoinConsecutiveFileChunks(chunks); 367 loader.LoadFileChunks(fileName, chunks, cpu); 368 } 369 370 // Name of the last parameter is kept as 'cpu' for backward compatibility. LoadELF(this IBusController loader, ReadFilePath fileName, bool useVirtualAddress = false, bool allowLoadsOnlyToMemory = true, ICluster<IInitableCPU> cpu = null)371 public static void LoadELF(this IBusController loader, ReadFilePath fileName, bool useVirtualAddress = false, bool allowLoadsOnlyToMemory = true, ICluster<IInitableCPU> cpu = null) 372 { 373 if(!loader.Machine.IsPaused) 374 { 375 throw new RecoverableException("Cannot load ELF on an unpaused machine."); 376 } 377 Logger.LogAs(loader, LogLevel.Debug, "Loading ELF file {0}.", fileName); 378 379 using(var elf = ELFUtils.LoadELF(fileName)) 380 { 381 var segmentsToLoad = elf.Segments.Where(x => x.Type == SegmentType.Load); 382 if(!segmentsToLoad.Any()) 383 { 384 throw new RecoverableException($"ELF '{fileName}' has no loadable segments."); 385 } 386 387 List<FileChunk> chunks = new List<FileChunk>(); 388 foreach(var s in segmentsToLoad) 389 { 390 var contents = s.GetContents(); 391 var loadAddress = useVirtualAddress ? s.GetSegmentAddress() : s.GetSegmentPhysicalAddress(); 392 chunks.Add(new FileChunk() { Data = contents, OffsetToLoad = loadAddress}); 393 } 394 395 // If cluster is passed as parameter, we setup ELF locally so it only affects cpus in cluster. 396 // Otherwise, we initialize all cpus on sysbus and load symbols to global lookup. 397 if(cpu != null) 398 { 399 foreach(var initableCpu in cpu.Clustered) 400 { 401 loader.LoadFileChunks(fileName, chunks, initableCpu); 402 loader.LoadSymbolsFrom(elf, useVirtualAddress, context: initableCpu); 403 initableCpu.InitFromElf(elf); 404 } 405 } 406 else 407 { 408 loader.LoadFileChunks(fileName, chunks, null); 409 loader.LoadSymbolsFrom(elf, useVirtualAddress); 410 foreach(var initableCpu in loader.GetCPUs().OfType<IInitableCPU>()) 411 { 412 initableCpu.InitFromElf(elf); 413 } 414 } 415 } 416 } 417 SortAndJoinConsecutiveFileChunks(List<FileChunk> chunks)418 private static List<FileChunk> SortAndJoinConsecutiveFileChunks(List<FileChunk> chunks) 419 { 420 if(chunks.Count == 0) 421 { 422 return chunks; 423 } 424 425 chunks.Sort((lhs, rhs) => lhs.OffsetToLoad.CompareTo(rhs.OffsetToLoad)); 426 427 List<FileChunk> joinedChunks = new List<FileChunk>(); 428 var nextOffset = chunks[0].OffsetToLoad; 429 var firstChunkIdx = 0; 430 431 for(var chunkIdx = 0; chunkIdx < chunks.Count; ++chunkIdx) 432 { 433 var chunk = chunks[chunkIdx]; 434 if(chunk.OffsetToLoad != nextOffset) 435 { 436 joinedChunks.Add(JoinFileChunks(chunks.GetRange(firstChunkIdx, chunkIdx - firstChunkIdx))); 437 firstChunkIdx = chunkIdx; 438 } 439 var chunkSize = chunk.Data.Count(); 440 nextOffset = chunk.OffsetToLoad + (ulong)chunkSize; 441 } 442 joinedChunks.Add(JoinFileChunks(chunks.GetRange(firstChunkIdx, chunks.Count - firstChunkIdx))); 443 444 return joinedChunks; 445 } 446 JoinFileChunks(List<FileChunk> chunks)447 private static FileChunk JoinFileChunks(List<FileChunk> chunks) 448 { 449 var loadOffset = chunks[0].OffsetToLoad; 450 var data = chunks.SelectMany(chunk => chunk.Data); 451 return new FileChunk() { Data = data, OffsetToLoad = loadOffset }; 452 } 453 454 private const char SRecStartOfRecord = 'S'; 455 private const int SRecStartOfRecordIndex = 0; 456 private const int SRecTypeIndex = 1; 457 private const int SRecBytesCountStart = 2; 458 private const int SRecBytesCountLength = 2; 459 private const int SRecAddressStart = 4; 460 private const int SRecChecksumLength = 2; 461 462 private enum HexRecordType 463 { 464 Data = 0, 465 EndOfFile = 1, 466 ExtendedSegmentAddress = 2, 467 StartSegmentAddress = 3, 468 ExtendedLinearAddress = 4, 469 StartLinearAddress = 5 470 } 471 472 private enum SRecPurpose 473 { 474 Header, 475 Data, 476 Count, 477 Termination, 478 } 479 480 private enum SRecType : byte 481 { 482 Header = (byte)'0', // S0 483 Data16BitAddress, // S1 484 Data24BitAddress, // S2 485 Data32BitAddress, // S3 486 Reserved, // S4 487 Count16Bit, // S5 488 Count24Bit, // S6 489 Termination32BitAddress, // S7 490 Termination24BitAddress, // S8 491 Termination16BitAddress, // S 492 } 493 } 494 } 495