classes/SegmentManager.ps1
|
class Chunk { [int]$Start [int]$Size [bool]$Virtual = $false [byte[]]$Bytes Chunk([int]$start, [int]$size, [byte[]]$bytes, [bool]$virtual) { $this.Start = $start $this.Size = $size $this.Virtual = $virtual $this.Bytes = $bytes } } class Segment { [string]$Name # Name of the segment [int]$Order # Index of the segment in definition order [int]$StartAddress # Get's overwritten/calculated when StartAfter is used [string]$StartAfter # Name of segment that this segment starts after [int]$LastAddress # If specified, segment cannot grow beyond this address [int]$Size # If specified, size of the segment [int]$RunAddress # Address where segment is loaded/executed from [int]$Align # Negative alignment implies fill before alignment from end [bool]$Fill # If true, fills to realEnd with FillByte [byte]$FillByte # Byte to use when filling [byte[]]$FillBytes # Bytes to use when filling [bool]$AllowOverlap # If true, allows other segments to overlap this one. StartAfter implies AllowOverlap on the segment referenced, unless Fill or negative Align is specified on the referenced segtment. [bool]$Virtual # If true, segment is virtual and does not emit bytes [int]$PC # Maintained as $RunAddress + $relativePC [int]$relativePC # Incremented as chunks are added [int]$relativeMaxPC # Tracks the max size reached in this segment [int]$realSize # The actual size of the segment after all chunks have been added and alignment/fill applied [int]$realStart # The actual calculated start address after layout is solved [int]$realEnd # The actual calcullated end address after layout is solved (Not redundant, as realStart may not be known) [int]$relativeMinPC # Lowest address used in this segment [int]$contentOffset # When negative alignment is used, this tracks where the content should be placed within the segment [System.Collections.Generic.List[object]]$Chunks # List of chunks in this segment Segment([string]$name) { $this.Name = $name $this.Align = 0 $this.Fill = $false $this.FillByte = 0 $this.FillBytes = @(0) $this.AllowOverlap = $false $this.Virtual = $false $this.relativePC = 0 $this.PC = $this.RunAddress + $this.relativePC $this.relativeMaxPC = -1 $this.relativeMinPC = -1 $this.realStart = -1 $this.realSize = -1 $this.realEnd = -1 $this.contentOffset = 0 $this.Chunks = [System.Collections.Generic.List[Chunk]]::new() } [void] AddChunk([Chunk]$chunk) { if ($this.Virtual) { $chunk.Virtual = $true } $this.Chunks.Add($chunk) $minPC = $this.relativeMinPC -lt 0 ? $this.relativePC : $this.relativeMinPC $this.relativeMinPC = [math]::Min($minPC, $this.relativePC) $this.relativePC += $chunk.Size $rstart = $this.realStart -lt 0 ? 0 : $this.realStart $this.PC = ($this.RunAddress -lt 0) ? ($rstart + $this.relativePC) : ($this.RunAddress + $this.relativePC) $maxPC = $this.relativeMaxPC -lt 0 ? $this.relativePC : $this.relativeMaxPC $this.relativeMaxPC = [math]::Max($maxPC, $this.relativePC) } [void] Reset() { $this.Chunks.Clear() $this.relativePC = 0 $this.PC = $this.RunAddress -ge 0 ? $this.RunAddress + $this.relativePC : $this.realStart -ge 0 ? $this.realStart + $this.relativePC : $this.relativePC $this.relativeMaxPC = -1 $this.relativeMinPC = -1 } [int] GetEffectiveBaseAddress() { # return ($this.RunAddress -lt 0) ? $this.realStart -lt 0 ? $this.StartAddress -lt 0 ? 0 : $this.StartAddress : $this.realStart : $this.RunAddress return $this.realStart -lt 0 ? $this.StartAddress -lt 0 ? 0 : $this.StartAddress : $this.realStart } [void] SetPC([int]$addr) { $this.relativePC = $addr - $this.GetEffectiveBaseAddress() $this.PC = $addr } [int] GetPC() { return $this.PC } } class SegmentManager { [hashtable]$Segments = @{} [Segment]$Current [System.Collections.Generic.Stack[Segment]]$Stack [int]$LowestAddress [int]$HighestAddress [int]$nextSegmentOrder = 0 SegmentManager() { $this.Stack = [System.Collections.Generic.Stack[Segment]]::new() $newSegment = [Segment]::New("default") $newSegment.StartAddress = 0x0000 $newSegment.StartAfter = $null $newSegment.LastAddress = -1 $newSegment.Size = -1 $newSegment.RunAddress = -1 $newSegment.Align = 0 $newSegment.Fill = $false $newSegment.FillByte = 0x00 $newSegment.FillBytes = @(0x00) $newSegment.AllowOverlap = $true $newSegment.Virtual = $false $newSegment.realStart = -1 $newSegment.realSize = -1 $newSegment.realEnd = -1 $this.Add($newSegment) $this.Set("default") } [void] Add([Segment]$segment) { if ($this.Segments.ContainsKey($segment.Name)) { throw "Segment '$($segment.Name)' already defined." } $segment.Order = $this.nextSegmentOrder++ $this.Segments[$segment.Name] = $segment } [void] Set([string]$name) { if (-not $this.Segments[$name]) { throw "Segment '$name' not defined." } $this.Current = $this.Segments[$name] } [void] Push() { $this.Stack.Push($this.Current) } [void] Pop() { if ($this.Stack.Count -eq 0) { throw "Segment stack underflow." } $this.Current = $this.Stack.Pop() } [void] Emit([byte[]] $bytes) { $this.Current.AddChunk([Chunk]::new( $this.Current.relativePC, $bytes.Count, $bytes, $this.Current.Virtual )) } [void] Reset() { foreach ($segment in $this.Segments.Values) { $segment.Reset() } $this.Set("default") } [void] SolveLayout() { # Initialize values to run forward solving # foreach ($seg in $this.Segments.Values) { # if ($seg.StartAddress -ge 0) { # $seg.realStart = $seg.StartAddress # } # } $changed = $true while ($changed) { $changed = $false $orderedSegments = $this.Segments.Values | Sort-Object -Property realStart, Order foreach ($seg in $orderedSegments) { $oldStart = $seg.realStart $oldEnd = $seg.realEnd $oldSize = $seg.realSize # Determine minimum size ### not needed $minSize = $seg.relativeMaxPC - $seg.relativeMinPC if ($minSize -lt 0) { $minSize = 0 } # Determine earliest start if ($seg.realStart -ge 0) { $start = $seg.realStart } elseif ($seg.StartAddress -ge 0) { $start = $seg.StartAddress + ($seg.relativeMinPC -lt 0 ? 0 : $seg.relativeMinPC) } else { $start = $seg.relativeMinPC } if ( $start -lt 0 ) { $start = 0 } # StartAfter override if ($seg.StartAfter) { $before = $this.Segments[$seg.StartAfter] if (-not $before) { throw "Unknown StartAfter segment '$($seg.StartAfter)'" } if ($before.realEnd -lt 0) { # cannot resolve yet, skip continue } $start = $before.realEnd + 1 } # Negative Align if ($seg.Align -lt 0) { # negative align implies fill $seg.Fill = $true $align = -$seg.Align # if LastAddress exists, anchor to it if ($seg.LastAddress -ge 0) { $latestStart = $seg.LastAddress - $minSize + 1 $alignedContentStart = $latestStart - ($latestStart % $align) $seg.realStart = $seg.StartAddress -ge 0 ? $seg.StartAddress : $alignedContentStart $seg.realEnd = $seg.LastAddress $seg.realSize = $seg.realEnd - $seg.realStart + 1 # Store where content should go within the segment $seg.contentOffset = $alignedContentStart - $seg.realStart } else { # no LastAddress -> anchor at minimal satisfying page end throw "Cannot find end of address space for segment '$($seg.Name)'" } } else { # Positive or no Alignment $align = $seg.Align $alignedContentStart = $align -gt 0 ? [math]::Ceiling($start / $align) * $align : $start $size = $minSize # Fill -> LastAddress sets hard end if ($seg.Fill -and $seg.LastAddress -ge 0) { $end = $seg.LastAddress $size = $end - $start + 1 $seg.contentOffset = $alignedContentStart - $start } elseif ($seg.Fill -and $seg.Size -ge 0) { $end = $start + $seg.Size - 1 $size = $seg.Size $seg.contentOffset = $alignedContentStart - $start } else { # No fill - alignment moves the segment start itself $start = $alignedContentStart $end = $start + $size - 1 $seg.contentOffset = 0 } $seg.realStart = $start $seg.realSize = $size $seg.realEnd = $end } # detect solved progress if ($seg.realStart -ne $oldStart -or $seg.realEnd -ne $oldEnd -or $seg.realSize -ne $oldSize) { $changed = $true } } } } [byte[]] BuildBinary() { $orderedSegments = $this.Segments.Values | Sort-Object -Property realStart, Order # Write-Host "Final Segment Layout:" # foreach ($segment in $orderedSegments) { # Write-Host " Segment '$($segment.Name)': Start=0x$('{0:X4}' -f $segment.realStart) Size=0x$('{0:X4}' -f $segment.realSize)" # } foreach ($seg in $orderedSegments) { if ($seg.LastAddress -ge 0 -and $seg.realEnd -gt $seg.LastAddress) { throw "Segment '$($seg.Name)' exceeds defined End at 0x$('{0:X4}' -f $seg.LastAddress) with actual end address 0x$('{0:X4}' -f ($seg.realEnd))" } if ($seg.Size -ge 0 -and $seg.realSize -gt $seg.Size) { throw "Segment '$($seg.Name)' exceeds defined Size of 0x$('{0:X4}' -f $seg.Size) with actual size 0x$('{0:X4}' -f $seg.realSize)" } } $binaryStart = [int]::MaxValue $binaryEnd = [int]::MinValue foreach ($seg in $orderedSegments) { if ($seg.Virtual) { continue } # If Fill → segment owns full range if ($seg.Fill) { $binaryStart = [math]::Min($binaryStart, $seg.realStart) $binaryEnd = [math]::Max($binaryEnd, $seg.realEnd) continue } # Otherwise → only chunks define emitted range foreach ($chunk in $seg.Chunks) { if ($chunk.Virtual -or $chunk.Size -le 0) { continue } $start = $seg.realStart + ($chunk.Start - $seg.relativeMinPC) $end = $start + $chunk.Size - 1 $binaryStart = [math]::Min($binaryStart, $start) $binaryEnd = [math]::Max($binaryEnd, $end) } } if ($binaryStart -gt $binaryEnd) { return [byte[]]@() # nothing emitted anywhere } $this.LowestAddress = $binaryStart $this.HighestAddress = $binaryEnd $binSize = $binaryEnd - $binaryStart + 1 $buffer = New-Object byte[] $binSize # Check for overlaps for ($i = 0; $i -lt $orderedSegments.Count; $i++) { $seg = $orderedSegments[$i] if ($seg.realSize -le 0) { continue } for ($j = $i-1; $j -ge 0; $j--) { $prevSeg = $orderedSegments[$j] if ($prevSeg.realSize -le 0) { continue } if ($seg.realStart -le $prevSeg.realEnd -and $prevSeg.realStart -le $seg.realEnd) { # overlap detected if (-not $prevSeg.AllowOverlap) { $addr = [math]::Max($seg.realStart, $prevSeg.realStart) throw "Segment '$($seg.Name)' overlaps '$($prevSeg.Name)' at address 0x$('{0:X4}' -f $addr)" } } } } # emit Fill bytes first foreach ($seg in $orderedSegments) { if (-not $seg.Fill) { continue } $fillVal = $seg.FillBytes $start = $seg.realStart - $binaryStart $end = $seg.realEnd - $binaryStart for ($i = $start; $i -le $end; $i++) { $buffer[$i] = $fillVal[($i - $start) % $fillVal.Count] } } # emit chunks foreach ($seg in $orderedSegments) { foreach ($chunk in $seg.Chunks) { if ($chunk.Virtual -or $chunk.Size -le 0) { continue } $dst = $seg.realStart + $seg.contentOffset + $chunk.Start - $binaryStart if ($dst -lt 0 -or ($dst + $chunk.Size) -gt $buffer.Count) { ### not an error when the layout is still not converged... ### realStart may have been updated by SolveLayout(), ### but the Chunk start addresses have not and will not be until next pass. ### For now, just skip and hope for future pass to resolve ### ?CONVERGENCE OUT OF NON-COMPLICATED BOUNDS ERROR! ### I should probably throw if current pass is > 1.... continue } for ($i = 0; $i -lt $chunk.Size; $i++) { $buffer[$dst + $i] = $chunk.Bytes[$i] } } } return $buffer } [string] DumpSegments() { $orderedSegments = $this.Segments.Values | Sort-Object -Property realStart, Order $lines = [System.Collections.Generic.List[string]]::new() foreach ($seg in $orderedSegments) { $start = $seg.realStart -ge 0 ? ('$' + ('{0:X4}' -f $seg.realStart)) : '????' $end = $seg.realEnd -ge 0 ? ('$' + ('{0:X4}' -f $seg.realEnd)) : '????' $size = $seg.realSize -ge 0 ? ('$' + ('{0:X4}' -f $seg.realSize)) : '????' $cur = $seg.Name -eq $this.Current.Name ? ' *' : ' ' $content = $seg.relativeMaxPC -lt 0 ? 0 : ($seg.relativeMaxPC - ($seg.relativeMinPC -lt 0 ? 0 : $seg.relativeMinPC)) $flags = [System.Collections.Generic.List[string]]::new() if ($seg.Virtual) { $flags.Add('Virtual') } if ($seg.Fill) { $flags.Add('Fill') } if ($seg.AllowOverlap) { $flags.Add('AllowOverlap') } if ($seg.Align -ne 0) { $flags.Add("Align=$($seg.Align)") } if ($seg.RunAddress -ge 0 -and $seg.RunAddress -ne $seg.realStart) { $flags.Add('Run=$' + ('{0:X4}' -f $seg.RunAddress)) } if ($seg.StartAfter) { $flags.Add("StartAfter=$($seg.StartAfter)") } $flagStr = $flags.Count -gt 0 ? " [$($flags -join ', ')]" : '' $fillStr = '' if ($seg.Fill) { $fillStr = ' fill=' + (($seg.FillBytes | ForEach-Object { '$' + ('{0:X2}' -f $_) }) -join ',') } $name = "$($seg.Name)$cur" $lines.Add(" $($name.PadRight(14)) $start..$end size=$size content=$('{0:X4}' -f $content) chunks=$($seg.Chunks.Count)$flagStr$fillStr") } return "Segments ($($this.Segments.Count)):`n" + ($lines -join "`n") } } |