Creating a bootable SD Card
Creating a bootable SD Card
Replace:
<BOOT_IMAGE>with the required boot image file name.<BOOTLOADER_OFFSET>with the required byte offset.<OFFSET_SECTORS>with<BOOTLOADER_OFFSET> / 512if usingddwithbs=512.- Device identifiers very carefully—selecting the wrong disk can overwrite a system drive.
Important warnings
These procedures write directly to a physical storage device. Selecting the wrong device may overwrite a system drive and cause permanent data loss.
- Run the commands with administrator or root privileges.
- Double-check the SD device identifier and capacity.
- Unmount or dismount all SD partitions before writing.
- The bootloader overwrites data beginning at the specified offset.
- Ensure that the bootloader fits before the next partition or protected area.
- Prefer an offset aligned to the card’s logical sector size, commonly 512 bytes.
Example conversion:
Offset in sectors = Offset in bytes / 512
For example, an offset of 32768 bytes corresponds to:
32768 / 512 = 64 sectors
The examples below use placeholders rather than assuming a particular offset.
Linux
1. Insert and identify the SD card
lsblk -o NAME,PATH,SIZE,MODEL,TYPE,MOUNTPOINTS
Example device:
/dev/sdb
Use the whole device, such as /dev/sdb, not a partition such as /dev/sdb1.
2. Unmount all card partitions
sudo umount /dev/sdb?*
Unmount only the partitions that belong to the SD card.
3. Write the bootloader
Using a byte offset:
sudo dd if=<BOOT_IMAGE> of=/dev/sdb bs=1 seek=<BOOTLOADER_OFFSET> \
conv=notrunc,fsync status=progress
For a sector-aligned offset, the following is normally faster:
sudo dd if=<BOOT_IMAGE> of=/dev/sdb bs=512 seek=<OFFSET_SECTORS> \
conv=notrunc,fsync status=progress
Example for an offset of 32 KiB:
sudo dd if=<BOOT_IMAGE> of=/dev/sdb bs=512 seek=64 \
conv=notrunc,fsync status=progress
Flush pending writes:
sync
4. Optional verification
sudo cmp -n "$(stat -c '%s' <BOOT_IMAGE>)" \
<BOOT_IMAGE> <(sudo dd if=/dev/sdb bs=1 skip=<BOOTLOADER_OFFSET> status=none)
Remove the card only after all writes have completed.
macOS
1. Insert and identify the SD card
diskutil list external physical
Example device:
/dev/disk4
Verify its size before continuing.
2. Unmount the entire card
diskutil unmountDisk /dev/disk4
3. Write the bootloader
Use the raw device, /dev/rdisk4, for better performance.
Using a byte offset:
sudo dd if=<BOOT_IMAGE> of=/dev/rdisk4 bs=1 seek=<BOOTLOADER_OFFSET> \
conv=notrunc
Using 512-byte sectors:
sudo dd if=<BOOT_IMAGE> of=/dev/rdisk4 bs=512 seek=<OFFSET_SECTORS> \
conv=notrunc
Example for an offset of 32 KiB:
sudo dd if=<BOOT_IMAGE> of=/dev/rdisk4 bs=512 seek=64 \
conv=notrunc
Flush pending writes:
sync
4. Optional verification
sudo dd if=/dev/rdisk4 bs=1 skip=<BOOTLOADER_OFFSET> \
count="$(stat -f '%z' <BOOT_IMAGE>)" 2>/dev/null | cmp - <BOOT_IMAGE>
A successful comparison produces no output.
5. Eject the card
diskutil eject /dev/disk4
Windows: Native PowerShell Method
This method uses PowerShell and .NET without installing third-party writing tools.
1. Identify the SD disk number
Open PowerShell as Administrator:
Get-Disk | Format-Table Number, FriendlyName, BusType, Size, PartitionStyle
Identify the card by its size and device name.
For example, if the card is Disk 3, its raw device path is:
\\.\PhysicalDrive3
Do not continue until the disk number has been verified.
2. Dismount the card’s volumes
List partitions and drive letters:
Get-Partition -DiskNumber 3 |
Format-Table PartitionNumber, DriveLetter, Size
For every assigned drive letter, dismount it. For example:
mountvol E: /p
Close File Explorer windows and any applications accessing the card.
3. Write the bootloader
Save the following as Write-Bootloader.ps1:
param(
[Parameter(Mandatory = $true)]
[string]$Image,
[Parameter(Mandatory = $true)]
[int]$DiskNumber,
[Parameter(Mandatory = $true)]
[UInt64]$Offset
)
$ErrorActionPreference = "Stop"
$imagePath = (Resolve-Path $Image).Path
$devicePath = "\\.\PhysicalDrive$DiskNumber"
$disk = Get-Disk -Number $DiskNumber
Write-Host "Target: $devicePath"
Write-Host "Device: $($disk.FriendlyName)"
Write-Host "Size: $($disk.Size) bytes"
Write-Host "Offset: $Offset bytes"
Write-Host "Image: $imagePath"
$answer = Read-Host "Type WRITE to continue"
if ($answer -cne "WRITE") {
throw "Operation cancelled"
}
$imageStream = $null
$deviceStream = $null
try {
$imageStream = [System.IO.File]::Open(
$imagePath,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read
)
if (($Offset + $imageStream.Length) -gt $disk.Size) {
throw "The image does not fit on the selected disk at this offset"
}
$deviceStream = [System.IO.FileStream]::new(
$devicePath,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::ReadWrite
)
[void]$deviceStream.Seek(
[Int64]$Offset,
[System.IO.SeekOrigin]::Begin
)
$imageStream.CopyTo($deviceStream)
$deviceStream.Flush($true)
Write-Host "Bootloader written successfully"
}
finally {
if ($deviceStream) {
$deviceStream.Dispose()
}
if ($imageStream) {
$imageStream.Dispose()
}
}
Run it from an elevated PowerShell terminal:
Set-ExecutionPolicy -Scope Process Bypass
.\Write-Bootloader.ps1 `
-Image .\<BOOT_IMAGE> `
-DiskNumber 3 `
-Offset <BOOTLOADER_OFFSET>
Example for a 32 KiB offset:
.\Write-Bootloader.ps1 `
-Image .\<BOOT_IMAGE> `
-DiskNumber 3 `
-Offset 32768
4. Safely remove the card
After the command succeeds, use the Windows “Safely Remove Hardware” function before removing the card.
Windows: Cygwin
Start Cygwin Terminal as Administrator.
1. Identify available disks
cat /proc/partitions
Cygwin typically exposes physical disks as devices such as:
/dev/sda
/dev/sdb
/dev/sdc
Identify the SD card by its capacity. Do not assume that /dev/sdb is always the card.
2. Dismount Windows volumes
Before writing, close applications using the card and dismount each assigned drive letter from an elevated Windows terminal:
mountvol E: /p
3. Write the bootloader
Using a byte offset:
dd if=<BOOT_IMAGE> of=/dev/sdX bs=1 seek=<BOOTLOADER_OFFSET> \
conv=notrunc,fsync status=progress
Using 512-byte sectors:
dd if=<BOOT_IMAGE> of=/dev/sdX bs=512 seek=<OFFSET_SECTORS> \
conv=notrunc,fsync status=progress
Example for an offset of 32 KiB:
dd if=<BOOT_IMAGE> of=/dev/sdX bs=512 seek=64 \
conv=notrunc,fsync status=progress
Replace /dev/sdX with the verified physical card device.
Windows: MSYS2
Run the MSYS2 terminal as Administrator. First obtain the Windows disk number using elevated PowerShell:
Get-Disk | Format-Table Number, FriendlyName, BusType, Size
Dismount any assigned card volumes:
mountvol E: /p
GNU dd can then be directed to the Windows physical-device path. For example, for Disk 3:
MSYS2_ARG_CONV_EXCL='*' dd if=<BOOT_IMAGE> \
of='//./PhysicalDrive3' bs=512 seek=<OFFSET_SECTORS> \
conv=notrunc,fsync status=progress
Example for a 32 KiB offset:
MSYS2_ARG_CONV_EXCL='*' dd if=<BOOT_IMAGE> \
of='//./PhysicalDrive3' bs=512 seek=64 \
conv=notrunc,fsync status=progress
MSYS2 raw-device handling can vary between runtime and coreutils versions. If the physical-device path is rejected, use the native PowerShell method above.
General verification using a checksum
The most reliable verification procedure is:
- Read exactly the size of
<BOOT_IMAGE>from the target offset. - Save it to a temporary file.
- Compare the SHA-256 hashes.
Linux example
size=$(stat -c '%s' <BOOT_IMAGE>)
sudo dd if=/dev/sdb of=bootloader-readback.bin \
bs=1 skip=<BOOTLOADER_OFFSET> count="$size" status=progress
sha256sum <BOOT_IMAGE> bootloader-readback.bin
macOS example
size=$(stat -f '%z' <BOOT_IMAGE>)
sudo dd if=/dev/rdisk4 of=bootloader-readback.bin \
bs=1 skip=<BOOTLOADER_OFFSET> count="$size"
shasum -a 256 <BOOT_IMAGE> bootloader-readback.bin
The two hashes must be identical.