Skip to content

Commit 32a0e5e

Browse files
executor: arm64: syzos: add flush_cache_range()
ARMv8-A architecture mandates how caches should be flushed when writing self-modifying code. Although it would be nice to catch some bugs caused by omitting this synchronization, we want it to happen in most cases, so that our code actually works.
1 parent e11f716 commit 32a0e5e

File tree

1 file changed

+32
-3
lines changed

1 file changed

+32
-3
lines changed

executor/common_kvm_arm64_syzos.h

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,38 @@ guest_main(uint64 size, uint64 cpu)
193193
guest_uexit((uint64)-1);
194194
}
195195

196+
// Some ARM chips use 128-byte cache lines. Pick 256 to be on the safe side.
197+
#define MAX_CACHE_LINE_SIZE 256
198+
199+
GUEST_CODE static noinline void
200+
flush_cache_range(void* addr, uint64 size)
201+
{
202+
uint64 start = (uint64)addr;
203+
uint64 end = start + size;
204+
205+
// For self-modifying code, we must clean the D-cache and invalidate the
206+
// I-cache for the memory range that was modified. This is the sequence
207+
// mandated by the ARMv8-A architecture.
208+
209+
// 1. Clean D-cache over the whole range to the Point of Unification.
210+
for (uint64 i = start; i < end; i += MAX_CACHE_LINE_SIZE)
211+
asm volatile("dc cvau, %[addr]" : : [addr] "r"(i) : "memory");
212+
// 2. Wait for the D-cache clean to complete.
213+
asm volatile("dsb sy" : : : "memory");
214+
215+
// 3. Invalidate I-cache over the whole range.
216+
for (uint64 i = start; i < end; i += MAX_CACHE_LINE_SIZE)
217+
asm volatile("ic ivau, %[addr]" : : [addr] "r"(i) : "memory");
218+
// 4. Wait for the I-cache invalidate to complete.
219+
asm volatile("dsb sy" : : : "memory");
220+
221+
// 5. Flush pipeline to force re-fetch of new instruction.
222+
asm volatile("isb" : : : "memory");
223+
}
224+
196225
GUEST_CODE static noinline void guest_execute_code(uint32* insns, uint64 size)
197226
{
227+
flush_cache_range(insns, size);
198228
volatile void (*fn)() = (volatile void (*)())insns;
199229
fn();
200230
}
@@ -236,9 +266,6 @@ GUEST_CODE static uint32 get_cpu_id()
236266
return (uint32)val;
237267
}
238268

239-
// Some ARM chips use 128-byte cache lines. Pick 256 to be on the safe side.
240-
#define MAX_CACHE_LINE_SIZE 256
241-
242269
// Read the value from a system register using an MRS instruction.
243270
GUEST_CODE static noinline void
244271
guest_handle_mrs(uint64 reg)
@@ -249,6 +276,7 @@ guest_handle_mrs(uint64 reg)
249276
uint32* insn = (uint32*)((uint64)ARM64_ADDR_SCRATCH_CODE + cpu_id * MAX_CACHE_LINE_SIZE);
250277
insn[0] = mrs;
251278
insn[1] = 0xd65f03c0; // RET
279+
flush_cache_range(insn, 8);
252280
// Make a call to the generated MSR instruction and clobber x0.
253281
asm("blr %[pc]\n"
254282
:
@@ -273,6 +301,7 @@ guest_handle_msr(uint64 reg, uint64 val)
273301
uint32* insn = (uint32*)((uint64)ARM64_ADDR_SCRATCH_CODE + cpu_id * MAX_CACHE_LINE_SIZE);
274302
insn[0] = msr;
275303
insn[1] = 0xd65f03c0; // RET
304+
flush_cache_range(insn, 8);
276305
// Put `val` into x0 and make a call to the generated MSR instruction.
277306
asm("mov x0, %[val]\nblr %[pc]\n"
278307
:

0 commit comments

Comments
 (0)