Building a payload
A payload is built by the same build-app.ps1 pipeline that produces an application module, with
the -Payload switch. This page covers the command, the output layout, the project options a
payload uses, and how the payload build differs from an application-module build.
The command
pwsh build/build-app.ps1 -ProjectPath samples/prospero-payload-unjail/SampleApp.csproj -Payload -Output Folder
-Payloadswitches the link to the payload kind. Without it, the build stops with an error asking for--kind eboot(the default for an application module).-Output Folderis the only supported output for a payload, and the default when-Payloadis given. A payload does not carrysce_sysmetadata and is not packed into a*.pkg.- The output is a single
SampleApp.elf(or<AssemblyName>.elf) under the project’sout/folder.
Pass -DiagnosticBreadcrumbs to include the CRT’s sp:* log checkpoints in the built ELF. Each
checkpoint issues SYS_kexec directly, so the line reaches the kernel log even when nothing else
is set up. Useful during device-log debugging, and safe to leave off for production.
The project
A payload project is a C# library project with a small set of extra properties:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<NativeLib>Static</NativeLib>
<AssemblyName>SampleApp</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(SharpProsperoRoot)/src/SharpProspero/SharpProspero.csproj" />
</ItemGroup>
</Project>
PublishAot=trueandNativeLib=Staticare what let the compiler produce a.othe SDK linker can turn into a payload. A payload is not an executable — it is a shared object the loader maps and jumps to — so theNativeLib=Staticsetting produces a static library the compile step emits.AllowUnsafeBlocks=trueis required because every payload interop path uses raw pointers.AssemblyNamedecides the output filename.
Two payload-only project properties add per-payload dependencies. See SPRX declarations.
| Property | What it does |
|---|---|
<ProsperoSprx Include="..." /> |
Declares an extra system library the payload calls. Emitted as a DT_NEEDED in the linked ELF. |
<ProsperoKernelSprx>libkernel_sys.sprx</ProsperoKernelSprx> |
Overrides the default kernel library (libkernel_web.sprx). Use libkernel_sys.sprx for a payload that needs the kernel-side POSIX surface (getmntinfo, hardware info, and so on). |
The output
A payload build writes:
out/SampleApp.elf— the payload itself.out/SampleApp.diag.elf(when-DiagnosticBreadcrumbsis on) — a copy with the CRT breadcrumbs compiled in. Send this one when the device log has to show the bring-up sequence.
The out/ folder is safe to delete and rebuild.
What the linker does
The payload link is different from an application-module link:
- Kind.
--kind payloadwrites anET_DYNELF with aPT_DYNAMICsegment holding the imports, the relocation tables, and the symbol tables. An application-module link writes aneboot.binwith a section table sized for the console’s own program loader. - Start code.
PayloadCrtEmitterwrites the payload’s start object. The application-module link uses a different start object —CrtEmitter— that expects the console loader to have set up imports and thread-local storage before callingmain. - Compat object. The compatibility object that bridges the C runtime extends each
mprotectlength by an extra page for a payload build, so the collector’s straddled bookkeeping page is covered. An application-module build uses the plain length. - Section headers. A payload includes no section table. The loader reads only the segment headers.
- DT_NEEDED. The default set is
libkernel_web.sprx,libSceLibcInternal.sprx,libSceNet.sprx— plus every entry from<ProsperoSprx>items in the project.<ProsperoKernelSprx>replaces the first entry.
The application-module and payload code paths through the toolchain are kept strictly separate, so a change on one side does not affect a build on the other. See Build pipeline for the three-step pipeline.
Rebuilding after a CRT change
A change to the CRT (SharpProspero.Link/PayloadCrtEmitter.cs), the payload writer, the linker, or
the compat emitter is enough to invalidate every built payload. Rebuild the samples you rely on
before sending them. Compare the built ELF against the SDK sample corpus with the elf command:
dotnet run --project tools/SharpProspero.Bindings.Generator -- elf --file out/SampleApp.elf --sizes
Iterating
While iterating on a payload, keep the console listening on the loader port and send the fresh build over each cycle:
- Edit the payload’s
Program.cs. - Rebuild:
pwsh build/build-app.ps1 -ProjectPath <project> -Payload -Output Folder. - Send:
dotnet run --project tools/SharpProspero.Bindings.Generator -- payload --send --host <ip> --file out/<name>.elf. - Watch the device log for the
sp:*breadcrumbs and the payload’s own output.
The default send port and the wire format are covered in Sending a payload.