# Boot SchOS, run the text-rendering self-test, and capture the result as an image. # # The display class cannot be checked without a display: the assertion is "did # the right pixels reach the screen", and the only instrument for that is a # screendump. This drives it through `echo t > /proc/sysrq` and takes a picture. # # What the pattern proves: four solid quadrants in theme colours (a stride error # would slant the bands, a bpp error would change the colours), a white frame one # pixel inside the edge (an off-by-one in the present rectangle would clip it), # and the fact that every pixel was drawn into RAM before a single byte reached # the framebuffer -- which is what double buffering means. # # QEMU's monitor is on its own socket so the serial console stays free for the # shell. screendump writes a PPM; the PNG conversion is optional and skipped if # ImageMagick is not around. # # tools/crashshot.ps1 crash the ramdisk-root build param( [int]$BootSeconds = 20, [string]$Out = "build/textshot.ppm", [string]$Qemu = (Find-Qemu) ) # QEMU, on whichever host this is. # # PowerShell 7 runs on Linux, macOS and the BSDs as well as Windows, so these # scripts are usable from all of them -- but the old default was a hardcoded # scoop path under $env:USERPROFILE, which exists on exactly one of those. PATH # first, because a package-managed qemu is on it everywhere; the scoop location # is only a fallback for the Windows install that does not add itself to PATH. function Find-Qemu { $exe = if ($IsWindows -or $env:OS -eq 'Windows_NT') { 'qemu-system-x86_64.exe' } else { 'qemu-system-x86_64' } $onPath = Get-Command $exe -ErrorAction SilentlyContinue if ($onPath) { return $onPath.Source } if ($env:USERPROFILE) { $scoop = Join-Path (Join-Path $env:USERPROFILE 'scoop/apps/qemu/current') $exe if (Test-Path $scoop) { return $scoop } } foreach ($p in @('/usr/bin', '/usr/local/bin', '/opt/homebrew/bin')) { $c = Join-Path $p $exe if (Test-Path $c) { return $c } } throw "qemu-system-x86_64 not found on PATH. Install QEMU, or pass -Qemu ." } $ErrorActionPreference = 'Stop' # Repo root is two levels up: this script lives in tools/windows/. $root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent $hda = Join-Path $root 'schos.img' # The root filesystem. SchFS is the only root there is: the kernel mounts # device 1 in forkret() and panics if it is not a SchFS volume, so this is # not a choice and there is no -SchFS switch any more. It used to default to # build/fs.img, which stopped being bootable the moment SchFS became root. $hdb = Join-Path $root 'build/schfs.img' $log = Join-Path $root 'build/crashshot.log' $ppm = Join-Path $root $Out $mon = Join-Path $root 'build/qemu-mon.sock' Remove-Item -Force $log, $ppm -ErrorAction SilentlyContinue # The monitor goes to a TCP port rather than a unix socket: PowerShell can open # a TCP client without any extra machinery, and QEMU on Windows has no unix # sockets anyway. $monPort = 55555 $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = $Qemu $psi.Arguments = @( "-drive file=`"$hda`",format=raw,index=0,media=disk" "-drive file=`"$hdb`",format=raw,index=1,media=disk" '-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2' '-display none' "-chardev stdio,id=c0,logfile=`"$log`"" '-serial chardev:c0' "-monitor tcp:127.0.0.1:$monPort,server,nowait" ) -join ' ' $psi.RedirectStandardInput = $true $psi.UseShellExecute = $false Write-Host "==> booting" -ForegroundColor Cyan $p = [System.Diagnostics.Process]::Start($psi) function Send-Line($text) { foreach ($ch in ($text + "`n").ToCharArray()) { $p.StandardInput.Write($ch) $p.StandardInput.Flush() Start-Sleep -Milliseconds 30 # serial has no flow control } } try { Start-Sleep -Seconds $BootSeconds # Show the device list first, so the transcript records what was bound. Write-Host " > listing devices" -ForegroundColor DarkGray Send-Line 'cat /proc/devices' Start-Sleep -Seconds 3 Write-Host " > echo t > /proc/sysrq" -ForegroundColor DarkGray Send-Line 'echo t > /proc/sysrq' Start-Sleep -Seconds 4 Write-Host "==> capturing the framebuffer" -ForegroundColor Cyan $client = New-Object System.Net.Sockets.TcpClient('127.0.0.1', $monPort) $stream = $client.GetStream() $writer = New-Object System.IO.StreamWriter($stream) $writer.AutoFlush = $true Start-Sleep -Milliseconds 500 # QEMU on Windows wants a native path here, not the one PowerShell shows. $writer.WriteLine("screendump $ppm") Start-Sleep -Seconds 2 $writer.WriteLine('quit') Start-Sleep -Milliseconds 500 $client.Close() } finally { if (-not $p.HasExited) { $p.Kill() } Start-Sleep -Milliseconds 800 } if (Test-Path $log) { $text = Get-Content $log -Raw Write-Host "==> serial transcript" -ForegroundColor Cyan # Only the crash report, not the whole boot. $idx = $text.IndexOf('display: presented') if ($idx -ge 0) { Write-Host $text.Substring($idx) } else { Write-Host $text } } if (Test-Path $ppm) { $size = (Get-Item $ppm).Length Write-Host "==> $ppm ($size bytes)" -ForegroundColor Green $png = [System.IO.Path]::ChangeExtension($ppm, '.png') $magick = Get-Command magick -ErrorAction SilentlyContinue if ($magick) { & magick $ppm $png Write-Host "==> $png" -ForegroundColor Green } } else { Write-Host "FAIL: no screendump was produced" -ForegroundColor Red exit 1 }