simulator/ runs firebreak against a simulated Dot. No Dot, real adb, real
fastboot or real serial port takes part.
simulator/run.sh OUT amonet-biscuit-v1.1.0-bboe
START=no-light simulator/run.sh OUT amonet-biscuit-v1.1.0-bboe --short
python3 simulator/trace.py OUT.jsonl # one command per line, for diff
python3 simulator/trace.py --commands OUT.jsonl # with the payload commands per rangerun.shmakes a new Dot, runs firebreak, and leavesOUT.log, the trace (OUT.jsonlandOUT.txt), a disk snapshot (OUT.snapshot) and the raw partition table (OUT.table: LBA 0 to 33, then the last 33 sectors).run.shdestroys the Dot when it exits, also on Ctrl-C orSIGTERM. First it stops firebreak and the loop that waits to plug the Dot in, and waits for both. Aplugin progress runs to its end. A Dot made withpython3 simulator/dot.py createstays untilpython3 simulator/dot.py destroy.run.shpasses its pid tocreate.createrefuses while therun.shthat the state names still runs.- It needs Docker,
timeout(coreutils, on macOS too), and the downloads in~/.cache/firebreak. The image build downloads busybox, pigz and toybox source and checks each by SHA-256. - firebreak's cache is
~/.cache/firebreak-sim-cache.dot.py createcopies each download into itsfirebreakfolder, as an APFS clone on macOS and with--reflink=autoelsewhere. Where a clone fails, as off APFS, it copies the file. So a run that writes a file in place changes only the copy. A copy whose size or modification time is not its download's is copied again. What a run builds there stays for the next run. - State and the trace are in
~/.cache/firebreak-sim.FIREBREAK_SIMandFIREBREAK_SIM_CACHEmove the two, so several Dots can run at once. Each Dot needs its own pair: firebreak keeps its boot0-erased marker in the cache. createrefuses aFIREBREAK_SIM_CACHE, or afirebreakfolder in it, that is the same directory as~/.cacheor~/.cache/firebreak, or as$XDG_CACHE_HOMEor itsfirebreakfolder. It compares device and inode, so another case, a link, a trailing slash or a relative path does not get past it. It also refuses whenHOMEis not set, and when thefirebreakfolder is a link. firebreak would otherwise write the real cache.START=fastboot, the default, starts in stock fastboot.START=no-lighterases boot0 and leaves the Dot unplugged.run.shplugs it in when firebreak prints "Short the Dot's test point".- firebreak's stdin is
/dev/null. Its stdout must be unbuffered: withoutPYTHONUNBUFFERED=1, the prompt reached the log only at exit, and the Dot was never plugged in. run.shstops ifadborfastbooton its PATH is not the simulator's, or if the virtualenv's port hook is not loaded.simulator/bin/adbandsimulator/bin/fastbootrundot.pyunder the virtualenv's interpreter, and fail when there is none. Through pyenv's shim,python3took 270 ms to start against 14 ms, and a run makes up to about 260 calls.- After firebreak exits,
run.shrunsdot.py haltbefore it copies the trace.haltsends the bootrom SIGTERM, which writes its open range, and waits up to 10 s for it to exit. It signals the pid only when that process runs this checkout'sbootrom.pyfor this state folder. Otherwise it drops the pid. The bootrom clears its pid from the state on every exit. createdestroys the Dot that the state names before it makes a new one.destroyhalts too, then takes the state's lock. It stops a bootrom that started after the halt, sets each of the Dot's loop devices writable, detaches it, and removes the container. It skips a loop whose backing inode, offset or size is no longer the Dot's.- A stopped container cannot run the detach, and its loops stay bound to the
deleted disk files. So
destroystarts a stopped container first. If the detach still fails, it copies the loop records out and runs the detach in a new privileged container of the same image, with the same checks. If that fails too, or the container is gone, it prints a warning with the records.
- The eMMC is files in a privileged container: the user area, boot0, boot1
and the RPMB.
sim-bootmakes a loop device for each table entry at each boot. - The container has no network (
--network none). firebreak itself runs on the host and keeps its network. - The container is privileged. So it sees the Docker VM's devices under
Docker Desktop, and the host's devices on a Linux host. At each boot
sim-bootremoves every block node from the container's/devand then makes nodes for its own loops only./devis the container's tmpfs, so this removes nothing on the host. It takes loop numbers from/dev/loop-control, so any number of Dots can run. - At each boot
sim-bootkills every process that the previous boot's commands left running, a leftoversleep infinitytoo. A Dot does not keep writing across a reboot. It spares pid 1, its oldest child (the container's command) andsim-boot, by pid. It spares the loop holder only while that pid runssim-holdfor this run folder. - Every
adb shellandexec-outcommand runs in the container as the uid and gid that the Dot's adbd gives it, under that environment'ssh. ItsPATHholds only that environment's commands. No GNU tool is on it, except bash where TWRP ships bash.
| environment | sh |
commands | uid |
|---|---|---|---|
| TWRP 3.2.3 | busybox | busybox; gunzip, gzip and unpigz are pigz |
0 |
| TWRP 3.7.0_9-0, 3.7.0_9-bboe2 | mksh | toybox; dd, grep, awk, unzip, bash and unpigz are their own |
0 |
| Fire OS 5 | mksh | toolbox's names, busybox, gzip; no md5sum or wc by name; su under Magisk only |
2000 |
| Fire OS 6, root adb | mksh | toybox; dd, ps, top are toolbox's; grep and gzip are their own; no gunzip |
0 |
- Each list is read from the image. TWRP's is each ramdisk's
/sbin. Fire OS 5's is/system/binof the image that the Fire OS 5 routes write (system-6ababc517529). Fire OS 6's is/system/binof stock 8146. - Every
/sbinname that a TWRP list leaves out is in/sim/flavours/*.missing, as is every name that a Fire OS list maps to toolbox or toybox and the simulator does not have. Other Fire OS programs that no tool calls, such asam,logcatandsvc, are not listed. - Fire OS 5 has no toybox. Its
/system/bin/busyboxis BusyBox v1.22.1, static, with 354 applets, and no name links to it. Somd5sum,wc,gunzipandzcatwork only asbusybox md5sumand so on. - The builds are the closest that run in the container, not the Dot's binaries:
| on the Dot | in the simulator |
|---|---|
| busybox 1.22.1, bionic | busybox 1.22.1, glibc, defconfig |
| pigz 2.2.5 | pigz 2.2.5 |
| toybox 0.7.6, Android's config | toybox 0.7.6, defconfig with dd, gzip, gunzip, zcat, tr and expr, on zlib |
| mksh, Android's | mksh R59c |
toolbox's dd, and 3.7.0's own |
toolbox-dd: refuses conv= with dd: conv option disabled, else runs toybox dd |
3.7.0's and Fire OS 6's own grep |
toybox grep |
3.7.0's own awk |
original-awk 2023-11-27 |
3.7.0's own unzip |
Info-ZIP unzip 6.0 |
| Fire OS 5's busybox 1.22.1, static | the busybox above: the 354 applet names match |
Fire OS 5's and 6's gzip (zlib's minigzip) |
minigzip from zlib 1.2.8 |
Fire OS 5's toolbox, Fire OS 6's ps and top |
toybox 0.7.6 under toolbox's names |
toolbox md5 and hd |
simulator/device/toolbox: AOSP 5.1's md5.c and hd.c in Python |
toolbox start, stop and log |
no-ops that exit 0 |
toolbox getevent |
waits for ever: there is no input device |
sgdisk, mke2fs, e2fsck, resize2fs, tune2fs |
gdisk 1.0.10, e2fsprogs 1.47.0 |
-
The base image is
ubuntu:24.04pinned by digest. The image build gets these packages, not pinned: cpio 2.15+dfsg-1ubuntu2.1, e2fsprogs 1.47.0-2.4~exp1ubuntu4.1, gdisk 1.0.10-1build1, mksh 59c-37, original-awk 2023-11-27-1, python3 3.12.3-0ubuntu2.1, python3-fusepy 3.0.1-5, unzip 6.0-28ubuntu4.1 and util-linux 2.39.3-9ubuntu6.6, as built on 2026-10-11. -
minigzip -dpasses input with no gzip magic through, as zlib'sgzreaddoes, and exits 0. -
Names that a ramdisk links but the simulator's build lacks are left out. The image lists them in
/sim/flavours/*.missing: mostly SELinux commands,testand[outside the shell,tar,diff,sttyand the SHA-2 sums. -
toybox 0.7.6's
gunzippasses input with no gzip magic through, ascatdoes, and exits 0. A stream cut short exits 1 withgzclose: Illegal seek. pigz 2.2.5 refuses input with no magic,not compressed -- skipping, and exits 2. -
toybox
ddtruncates its output atseek, and a block device answersftruncate: Invalid argument. toolbox'sddwrites a block device whole, as overdub's rooting.md measured. Sotoolbox-ddaddsconv=notruncwhen the output is a block device. -
glibc's
realpathfails on a missing last component, so busyboxreadlink -fprints nothing for it. Bionic's prints the path, whichfix-symlinks.shrelies on. The simulator models that script's result rather than running it. -
/data/local/tmpisroot:shell0771under Fire OS 5, as overdub's pitfalls measured. A push there lands0666. Elsewhere a push lands as the container'sumaskleaves it. -
Device nodes are
root0600, so uid 2000 can neither read nor write them./proc/sys/vm/drop_cachesandforce_rorefuse uid 2000 too. These modes are Linux's and Android's defaults, not read from a Dot. -
Each boot is decided from the disk: the preloader in boot0, the LK in
lk_a, the TWRP inrecovery, the markers inexpdbandmisc, the build insystem_X, and Magisk inuserdata.
adb,fastboot,getprop,setprop,su,idunder Fire OS,bcbtool,pm,dumpsys,getenforceandrebootare stubs.twrp installwrites a Fire OS 6 build for an OTA zip and marks root adb for any other zip.suis a setuid binary. It gives root only while Magisk's database is inuserdata, and Fire OS 5 has it onPATHonly then. Magisk's busybox is not put first onPATHundersu.sureads its words withgetopt_long, the options and the option string of Magisk 17.3'snative/jni/su/su.c(tagv17.3). As there, getopt moves the words that are not options to the end.-ctakes the command from the word beforeoptind, sosu 0 -c id xrunsid xandsu -cidruns-cid. The first word left after the options, and after a-, is the user. Sosu 0 -candsu root -cset the uid.-cnand-mmare rewritten to-zand-M, and-uprints the usage and exits 2.- Not as Magisk: glibc's
getopt_long, where Magisk has bionic's; names other thanroot,systemandshellread as a number, where bionic knows every Android id; and-s,-l,-pand-Mchange nothing. Its own PTY, which Magisk opens when stdin is a terminal, is not modelled. It exits with the command's status. - Under uid 2000,
setpropsets onlydebug.*,log.*,service.adb.root,service.adb.tcp.portandsys.powerctl, which AOSP 5.1 lets the shell set. Any other name is ignored andsetpropexits 0. A setro.name does not change.sys.powerctlset toreboot,Xreboots into X. rebootsetssys.powerctl, as 5.1'srebootdoes, so the shell can reboot. It returns at once, where 5.1's waits for the reboot./sdcardand/data/local/tmpare container folders. They have no size limit, and a format ofuserdatadoes not empty them./sdcardis0777: its FUSE modes are not modelled.- boot0 holds a preloader when it starts
EMMC_BOOTand holdsBRLYTat 0x200. Otherwise the Dot boots into the bootrom. So amonet v1.1.0'sEMMC_BOOTand zeros, and firebreak's zeros, both reach it. Nothing else in boot0 is checked: not the preloader's own header or its signature, and not boot1. lk_b,tee1andtee2take no part in a boot. Onlylk_adecides the LK.- The fastbrick writes amonet v2.0.0's chain directly. It runs no exploit.
- amonet v1.1.0's LK clears
FASTBOOT_PLEASEinexpdbwhen it acts on it. This is inferred:fastboot-step.shreaches TWRP with the marker still written, so the marker cannot hold the Dot in fastboot.
- Their loop devices are read-only from the start of each boot, and again
after each
fastboot flashoreraseof them. /sys/block/mmcblk0bootNis a FUSE file system withforce_roandsize, over a tmpfs. A boot fails unless the FUSE file system is the top mount there. So a write cannot reach a Linux host's own/sys/block.- A write to
force_rosets the loop device read-only or writable before it returns. Soecho 0andecho 1hold for any writer:dd,toybox dd, or a shell redirect. - The value is read as the Dot's kernel, Linux 3.18.19, reads it: with
simple_strtoulin base 0. A number other than 0 sets read-only, and text with no number first isEINVAL. So1_0sets read-only, and-1and a space before1areEINVAL. Later kernels usekstrtoul, which refuses1_0. - A read gives the loop device's flag. So after a direct
blockdev --setrwit reads 0. - Only root can write
force_ro.
- Each TWRP replaces its by-name bootloader nodes at boot, as the script in its ramdisk does. A write through one does not reach the disk.
- 3.2.3 (
/sbin/fix-symlinks.sh):lk_a,lk_b,tee1andtee2are/dev/null, andX_realnames the partition.boot_aandboot_bnameboot_a_xandboot_b_x, andboot_X_amonetnames amonet's. With noboot_X_x,boot_Xis a link to the missingby-name/boot_X_x. - 3.7.0_9-0 and 3.7.0_9-bboe2 (
/sbin/slot-symlinks.sh):lk_a,lk_b,tee1,tee2,teeandpreloaderare files in/tmp/ota-decoy, at 1, 1, 5, 5, 5 and 4 MiB.X_realnames each one that was a partition. /tmp/ota-decoyis a 64 MiB tmpfs, so addwith nocountthere stops.- bboe2 also points
boot_aandboot_batboot_a_xandboot_b_xwhen they exist, and keeps amonet's asboot_X_amonet. - Not modelled:
/dev/block/other-lk,other-systemandother-boot,/dev/block/current-bootandcurrent-system,/dev/block/bootdevice,/dev/block/by-nameand/dev/block/platform/bootdevice. A command that names one finds nothing. - adb answers 5 s before MTP is on. Until then
sys.usb.configisadbandro.twrp.versionis not set. The 5 s is from the ring's timing in rooting.md. - The switch to
mtp,adbdrops USB for 1 s. A call made then fails with adb's error text, andadb deviceslists nothing. A shell call running across the switch runs to its end, prints the first half of its output and exits 255, or 0 without the shell protocol. A push fails withfailed to read copy response: EOF. The trace marks each one# cut by the USB switch. None of this is measured: not the 1 s, the command running on, the half, the status or the text.
- The
adbdof TWRP 3.2.3 (5.1.1,LMY49M) and of Fire OS 5.5.5.4 (from its OTA'sboot.img) list noshell_v2feature and holdcreate_subproc_pty. Theadbdof both 3.7.0 TWRPs and of Fire OS 6 listshell_v2. Each was read from its image. - So under 3.2.3 and Fire OS 5, adb has no shell protocol. adb 37's client
(
client/commandline.cpp) then:- exits 0 from
adb shellwhatever the command did, and 1 only when it cannot connect; - never tells the Dot that stdin ended, so a command that reads stdin to its end never ends;
- refuses a service string over 4096 bytes with
error: shell command too long, and-Twitherror: device only supports allocating a pty.
- exits 0 from
- 5.1's
adbd(adb/services.c) runs ashell:command on a PTY, which it leaves in the kernel's default mode, and anexec:command on a socket pair. So the output has CR LF, and the line discipline acts on stdin: it echoes it, edits lines, and sendsSIGINT,SIGQUITandSIGTSTPfor 0x03, 0x1c and 0x1a.sim-ptyruns the command so, on a PTY in the container with the container kernel's default mode. - That PTY is the container's kernel, Linux 6.x, not the Dot's 3.18.19. It
passes all output that the command wrote before the PTY closes. The Dot's
3.18 kernel and Lollipop's
adbdcan lose the tail of the output. The simulator never loses it, and models no such loss. - When the host's
adbexits,sim-ptyhangs the PTY up, asadbddoes when it closes the PTY. Thatadbdcloses it then is inferred. exec-outexits 0 in every environment: the client never reads a status. The command's stdin never ends there either.- With the shell protocol, under 3.7.0_9-0, bboe2 and Fire OS 6,
adb shellpasses the status, as overdub's rooting.md measured for 3.7.0.-nends the command's stdin at once. Otherwise the shim passes stdin and then ends it. - adb's own options are read only before the command, as adb's
getoptwith+does:adb shell ls -trunsls -t. - 3.2.3's
__bionic_open_tzdatalines are not modelled. Nor is what-xmakes a protocoladbddo.
- A locked LK refuses
flashanderasewiththe command you input is restricted on locked hw, which a locked v1.1.0 LK printed. flashof an image larger than its partition fails withsize too large.flashoreraseof a name that is not in the table, nor boot0 or boot1, fails withunknown partition. Neither text is measured.-Ssets the size above whichflashreportsSending sparse, with the count of pieces. The write is the same either way. Without-San image is sent whole.oem reboot-recoveryreboots into recovery on any LK, locked or not. That a locked LK accepts it is not measured.
- The bootrom is
simulator/bootrom.py, on a pseudo-terminal, started at the boot into the bootrom. It runs no code. It answers the handshake,0xd1,0xd4and0xc8. - The crypto engine at
0x10210000answers function 126 only after the acquire words, with length 1 and slot pointers 18, 26, 26. It then writes the input words XOR the zero decryption to the destination. - Before
0x102868holds the range-check pattern, word access below0x10000000is refused. So a payload upload or a jump without the defeat fails. - A write to
0x1028A8jumps. The bootrom compares the memory at0x201000with thepayload.binof each amonet zip. It answers as the payload that matches, and stays silent for any other code.
- The payload speaks its own command set: v1.1.0's six commands, or the twelve that v2.0.0's client sends.
0x5002sends v2.0.0's 512-byte test pattern, so its client's fast-send check passes.0x5003answers the acknowledgement.- Block commands go to the disk files through
sim-emmcin the container. The RPMB is the first 256 bytes of its file. A new Dot's RPMB startsAMZN. 0x1003and0x1004take 1 to 64 blocks, as amonet's client allows. A count outside that, a read or write past the end of its area, or any access to an area other than 0, 1 and 2 halts the payload: it logshaltedand answers nothing more. What the real payload does there is not known.0x5000reads any address at any length. Whether the real payload bounds either is not known.- The
0x3000reboot hands the Dot back to the boot rules, 3 s later. - The port is not modelled: the short, the watchdog, the preloader's
0e8d:2000port and USB packet sizes do not exist here. ports.pyalso replacesfirebreak.identify.usb_devices, so a run never sees the host's own USB devices. It lists the simulated Dot asAEOBCwhen it is booted, adbunauthorizedtoo, asEcho Dot (2nd Gen)in TWRP 3.7.0 and asEcho Dot 2nd Genin TWRP 3.2.3, and lists nothing in any other mode.
The simulator accepts these, where a real Dot may not:
- a jump without the cache-disable command (
0xC8 0xB1) - a crypto engine that ignores SOURCE and does not need the key and IV slots cleared
- root on Fire OS 5 decided by
magisk.dbalone - amonet v2.0.0's LK reading unlocked whatever the TEE holds
- any LK but stock booting any known TWRP
- the fastbrick on an unlocked LK
- the fastbrick leaving boot0 as it was; the real one writes the preloader
-sandANDROID_SERIAL, which it records and ignoresddpast 2 GiB ofmmcblk0under bboe2 and 3.7.0_9-0, where the Dot'sddanswersEINVAL
dot.py createmakes a virtualenv in~/.cache/firebreak-sim/venv. A.pthfile there importsports.pyat every start, so the children that a tool starts withsys.executablesee the port too, whatever theirPYTHONPATH.ports.pyreplacesserial.tools.list_ports.comportswhen pyserial loads. It lists only the simulated bootrom,0e8d:0003, and only while the Dot is in its bootrom. It never lists a real port.- Under Python 3.9, pyserial from the wheel loads through
zipimporter, which has noexec_module. So the hook runs the module's code fromget_code. - The bootrom keeps the pseudo-terminal's other end open, so a tool can close and reopen the port.
FIREBREAK_SIM_SPEEDsets the speed, K.run.shsets 20 when it is not set. At 1 or less the hook changes nothing.- The hook replaces
time.monotonic,time.perf_counter,time.time, their_nsforms,time.sleep,select.selectand theselectmethod of eachselectorsclass. It does this in every process the virtualenv's interpreter starts: the tool, its children,dot.pyandbootrom.py.run.shrunsplug,halt,snapshotandtableunder that interpreter too. - The clock runs K times faster than real time, except while a process holds
it. Then it runs at real time in every process. A
dot.pycall holds it from its start to its exit, except while it sleeps.bootrom.pyholds it for each eMMC access. So work in the container takes real time, and so does a tool timeout that waits for it. clock.json, in the state folder, holds the anchor in real and in clock time, the offset to wall time, and the pids that hold the clock. A change replaces the file whole, underclock.lock. Each change drops the pids that are gone, so a call that a tool kills holds the clock only until the next change.createdeletes the file.- Each trace line's
timeis the clock's, not real time, except the lines thatcreatewrites:run.shruns it without the hook. - A sleep or a wait for a descriptor sleeps at most 50 ms of real time at once, so a change of speed takes effect within 50 ms.
- pyserial keeps
time.monotonicas a class attribute and calls it throughself. A plain function there getsselfas an argument, so the clock's functions are bound methods.
These stay in real time:
Event.wait,Lock.acquireandCondition.waitwith a timeout. firebreak uses them only for its progress line, every 0.1 s, and for its cache lock, in a loop with no deadline.- Socket timeouts. Only downloads use them, and the cache holds every download.
time.strftimein firebreak's log. It only prints.run.sh:timeout 1500, and the 0.5 s poll for the short prompt.
Work outside a hold counts K times: the interpreter's start before the hook,
firebreak's own work, and the pseudo-terminal. With 6 Dots at once a call to
adb or fastboot spends about 100 ms there, 2 s at K=20. These timeouts
bound K:
- firebreak kills
fastboot flash brickafter 8 s, as a Dot needs. At K=20, with 6 Dots at once, the call took 1.6 to 2.8 s to start and the chain 0.8 to 1.3 s more. At K=100, 1 of the 3 runs from stock fastboot was killed before the reboot, and at K=200 all 3 were. Each then stopped with "the Dot has been stock-fastboot for 10 minutes". - firebreak waits 5 s for a 64-block payload read. At K=100 and at K=200, 1 of the 3 runs from no light stopped there, with 25,550 and 26,572 of 32,772 bytes.
- At K=20 all 18 runs rooted, 6 at a time. At K=100, 2 of 6 stopped, and at K=200, 4 of 6.
Measured on 2026-10-12, firebreak to each target from no light with --short
and from stock fastboot, 6 Dots at once. Each Dot had its own FIREBREAK_SIM
and FIREBREAK_SIM_CACHE. A time is from the start of run.sh to its
summary line, and includes create and snapshot.
| run | K=1 | calls | K=20 | calls | K=20 | calls |
|---|---|---|---|---|---|---|
| v1-bboe, no light | 73 s | 181 | 60 s | 165 | 58 s | 165 |
| v1, no light | 75 s | 191 | 58 s | 175 | 60 s | 175 |
| v2, no light | 103 s | 275 | 60 s | 249 | 60 s | 248 |
| v1-bboe, fastboot | 87 s | 186 | 64 s | 164 | 64 s | 164 |
| v1, fastboot | 87 s | 186 | 64 s | 164 | 64 s | 164 |
| v2, fastboot | 63 s | 65 | 30 s | 51 | 28 s | 51 |
- The 6 runs took 103 s at K=1 and 64 s at K=20, both times.
- A run makes fewer calls at a higher K. A call's start counts K times, so fewer polls fit in a wait.
- At K=20 and K=100, each run's trace, snapshot and raw table matched the run at K=1, but for the number of polls.
- Every
adbandfastbootcall, boot and reboot goes totrace.jsonl, one locked write per line, with the whole command line, global flags included. Each call goes in when it starts, so a call that a tool kills still shows. A push and afastboot flashcarry the file's md5. - A shell call that passes stdin adds a second line when it ends,
# stdin sent BYTES:MD5 received BYTES:MD5: COMMANDintrace.py's output. sentcounts the bytes that the host's shim wrote intodocker exec.receivedcounts the bytes that arrived in the container.sim-stdincounts them there, betweendocker execand the command'sshorsim-pty, and passes them on through a pipe. Neither counts what the command read.received unknownmeanssim-stdindid not report, as when a reboot killed it.- When the command exits before it reads all of its stdin,
receivedcan be less thansent: the rest is still in Docker. When the command reads to its end, areceivedless thansentis bytes that Docker lost. - The bootrom logs each command with its address and words, and a refused
read32orwrite32asrefusedwith its count. An upload of more than 16 words logs its count and md5. - The payload logs reads and writes as an area, a first block, a count and
the md5 of the bytes. Contiguous reads, or contiguous writes, join into one
range.
--commandsshows the commands each range took. It also shows the watchdog kicks and cache reads sent between ranges, asbetween ranges. trace.pyputs$CACHEfor the simulator's cache folders and~/.cache/firebreak, and$TMPfor temporary names. It dropsadb devices,adb get-stateandfastboot devices;--keep-pollskeeps them.adb shell -n getprop NAME,fastboot getvar NAMEoradb wait-for-X, the whole line and nothing more, is not printed again when it is the same as the last line printed. A dropped poll between them does not count, so the number of polls before TWRP is up does not show. Every other line is printed each time, compound commands included, so a command sent twice shows twice.
dd if=/dev/zeroonto a regular file with nocountdoes not stop at the file's end. One filled Docker's disk. The container was gone, but its loop devices still held the deleted file, so the space came back only after a Docker restart.- A loop that
losetupattached and that then fails its backing check, or itsblockdev, is detached before the boot fails. A loop whose backing inode is not the Dot's is left alone. - A loop device's read-only flag outlives
losetup -d. A loop that was boot1 at one boot came back asboot_bat the next and refused every write withOperation not permitted. Loop numbers are shared by every container on the host, too. Sosim-bootsets each loop writable when it attaches it and before it detaches it, anddestroydoes the same. - A container removed without
destroyleaves its loop devices attached in the Docker VM.losetup -ain any privileged container lists them. - busybox and toybox
umountfree the loop device under what they unmount, where on a DotLOOP_CLR_FDfails on an eMMC partition. Freed, the loop number went to another Dot's partition: one Dot'ssgdiskthen read a 768 MiB disk with no table, or a disk of 0 sectors, and one snapshot readsystem_athroughrpmb. Sosim-holdkeeps every loop open for the boot, and the kernel only marks a busy loop to clear when it closes. sim-holdwrites to/sim/run/holder.log. A boot fails unless it holds every loop.factsandsnapshotfail unless it still runs.- FUSE 2 refuses a mount point that is not empty, so
/sys/blockis mounted withnonempty. docker exec -u 2000runs with gid 0, so aroot:shell0771folder refuses it. The simulator passes2000:2000.- The ext4 file systems differ on every run. The snapshot compares their file trees, not their bytes.