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