1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use ;
use VarIntReader;
/// First four bytes of a patch file.
pub const PATCH_MAGIC: u32 = 0xB1DF;
// Assembly implementation of the patch overwriter (`__patcher_overwrite`).
//
// The overwriter is responsible for self-modifying the currently running code in memory with the
// new version built at 0x07E00000 by the first patcher stage.
//
// In other words, this code is responsible for actually "applying" the patch.
global_asm!;
global_asm!;
// Linkerscript Symbols
//
// All of these external symbols are defined in our linkerscript (link/v5.ld) and don't have real
// types or values, but a pointer to them points to the address of their location defined in the
// linkerscript.
unsafe extern "C"
/// Internal patcher state representing what the patcher is attempting to do.
/// Differential Upload Patcher
///
/// This function builds a modified version of the user program in memory with a binary patch
/// (generated by `bidiff` in cargo-v5) applied to it, then overwrites (self-modifies) the current
/// program with the newer version. This allows us to only upload a binary diff of what has changed
/// from the original (the "base") file, which is significantly smaller than reuploading the entire
/// binary every time.
///
/// # Overview
///
/// Patching is performed in two steps. The first step is performed by this function, and involves
/// building the "newer" binary using the uploaded patch file and the current program as a
/// reference. The second step of patching is performed by the `__patcher_overwrite` assembly
/// routine, which is responsible for actually self-modifying the current program's active memory
/// and preparing CPU1 to re-run the now patched program memory.
///
/// ## Stage 1 - Building the new binary
///
/// The first stage of patching involves building the new (patched) binary. When `cargo-v5` uploads
/// a new patch file to the brain, we tell it to load the file into memory at address `0x07A00000` —
/// 6mb from the end of the RWX memory block allocated to user code. This 6mb region of memory
/// spanning `0x07A00000`..`0x80000000` is reserved specifically for the patcher to use and is split
/// into three 2mb subregions (which will be discussed later).
///
/// In order to actually use the patch file to build a new binary, we need a reference to the
/// original "base binary" that it's patching over. The program running on the brain before the
/// patch is applied is always the original binary, so in the `_boot` routine (vexide's assembly
/// entrypoint) we preemptively make a copy of the currently running binary before any Rust code
/// gets the chance to modify a writable section of the binary like `.data` or `.bss` (which would
/// corrupt the patch). This unmodified copy of the old binary is copied to address `0x07C00000`
/// (2mb after where our patch is loaded).
///
/// Finally, using the copy of the old binary and the patch, we are able to apply bidiff's
/// [`bipatch` algorithm](https://github.com/divvun/bidiff/blob/main/crates/bipatch/src/lib.rs) to build the new
/// binary file that we will run. This new binary is built at address `0x07E00000` (4mb after where
/// our patch is loaded).
///
/// ## Stage 2 - Overwriting the old binary with the new one
///
/// The second stage of the patch process is handled by the `__patcher_overwrite` assembly routine.
/// This routine is responsible for overwriting our currently running code with that new version we
/// just built. This involves memcpy-ing our new binary at `0x07E00000` to the start of user memory
/// (`0x03800000`). Doing this is far easier said than done though, and requires some important
/// considerations as to not shoot ourselves in the foot:
///
/// - When overwriting, it is *absolutely imperative* that the overwriter not depend on memory that
/// is in the process of being overwritten. This means that the overwriting routine must be done
/// entirely from a fixed address in memory and not reference any outside functions or data. We
/// ensure this by writing the entire stage-2 patch routine and memcpy implementation in assembly
/// and placing these instructions in a linker section (`.text.overwriter`) at a fixed address in
/// memory.
///
/// - There is also a potential opportunity for soundness problems if the overwriter modifies itself
/// while running. This is more unlikely (due to the next point), but has bad implications if the
/// patcher self-modifies its own instructions while in the process running. Writing this part in
/// assembly also ensures this won't happen, because assembly will always compile down to the same
/// instructions regardless of how we tell our compiler to optimize our code. This routine will
/// not change.
///
/// - Finally, there is the problem of cache coherency. ARMv7-A, like most modern architectures, caches
/// instructions fetched from memory into an on-chip L1 cache called the icache. There is also a similar
/// L1 cache called the dcache for memory/data accesses. ARM's cache model does not guarantee L1 cache will
/// always be synchronized (coherent) with what's actually present in physical memory when working with
/// self-modifying code, so we need to explicitly "clean" the instruction cache to bring it back to a
/// [point-of-unification (PoU)](https://developer.arm.com/documentation/den0013/d/Caches/Point-of-coherency-and-unification)
/// with physical memory, ensuring that when we jump back to the start of our program, we are actually
/// executing from the newly overwritten memory rather than stale instructions from icache. This process
/// of invalidating and cleaning instruction caches is described to further detail in
/// [ARM's documentation](https://developer.arm.com/documentation/den0013/latest/Caches/Invalidating-and-cleaning-cache-memory).
///
/// # Safety
///
/// The caller must ensure that the patch loaded at 0x07A00000 has been built using the currently
/// running binary as the basis for the patch.
pub unsafe
/// `bipatch` Algorithm
///
/// This function writes into `new` given an older `base` reference buffer and a [bidiff]-compatible
/// differential patch buffer that is designed to be applied over the base to build a new binary.
///
/// [bidiff]: https://github.com/divvun/bidiff
///
/// This is essentially a port of <https://github.com/divvun/bidiff/blob/main/crates/bipatch/src/lib.rs>
// NOTE: LLVM should always inline this function since it's only called once.