Skip to content

Commit

Permalink
target/arm: Handle IC IVAU to improve compatibility with JITs
Browse files Browse the repository at this point in the history
Unlike architectures with precise self-modifying code semantics
(e.g. x86) ARM processors do not maintain coherency for instruction
execution and memory, requiring an instruction synchronization
barrier on every core that will execute the new code, and on many
models also the explicit use of cache management instructions.

While this is required to make JITs work on actual hardware, QEMU
has gotten away with not handling this since it does not emulate
caches, and unconditionally invalidates code whenever the softmmu
or the user-mode page protection logic detects that code has been
modified.

Unfortunately the latter does not work in the face of dual-mapped
code (a common W^X workaround), where one page is executable and
the other is writable: user-mode has no way to connect one with the
other as that is only known to the kernel and the emulated
application.

This commit works around the issue by telling software that
instruction cache invalidation is required by clearing the
CPR_EL0.DIC flag (regardless of whether the emulated processor
needs it), and then invalidating code in IC IVAU instructions.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1034

Co-authored-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: John Högberg <john.hogberg@ericsson.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 168778890374.24232.3402138851538068785-1@git.sr.ht
[PMM: removed unnecessary AArch64 feature check; moved
 "clear CTR_EL1.DIC" code up a bit so it's not in the middle
 of the vfp/neon related tests]
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
  • Loading branch information
2 people authored and pm215 committed Jul 6, 2023
1 parent 1f51573 commit 9719f12
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 3 deletions.
11 changes: 11 additions & 0 deletions target/arm/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,17 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
return;
}

#ifdef CONFIG_USER_ONLY
/*
* User mode relies on IC IVAU instructions to catch modification of
* dual-mapped code.
*
* Clear CTR_EL0.DIC to ensure that software that honors these flags uses
* IC IVAU even if the emulated processor does not normally require it.
*/
cpu->ctr = FIELD_DP64(cpu->ctr, CTR_EL0, DIC, 0);
#endif

if (arm_feature(env, ARM_FEATURE_AARCH64) &&
cpu->has_vfp != cpu->has_neon) {
/*
Expand Down
47 changes: 44 additions & 3 deletions target/arm/helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -5234,6 +5234,36 @@ static void mdcr_el2_write(CPUARMState *env, const ARMCPRegInfo *ri,
}
}

#ifdef CONFIG_USER_ONLY
/*
* `IC IVAU` is handled to improve compatibility with JITs that dual-map their
* code to get around W^X restrictions, where one region is writable and the
* other is executable.
*
* Since the executable region is never written to we cannot detect code
* changes when running in user mode, and rely on the emulated JIT telling us
* that the code has changed by executing this instruction.
*/
static void ic_ivau_write(CPUARMState *env, const ARMCPRegInfo *ri,
uint64_t value)
{
uint64_t icache_line_mask, start_address, end_address;
const ARMCPU *cpu;

cpu = env_archcpu(env);

icache_line_mask = (4 << extract32(cpu->ctr, 0, 4)) - 1;
start_address = value & ~icache_line_mask;
end_address = value | icache_line_mask;

mmap_lock();

tb_invalidate_phys_range(start_address, end_address);

mmap_unlock();
}
#endif

static const ARMCPRegInfo v8_cp_reginfo[] = {
/*
* Minimal set of EL0-visible registers. This will need to be expanded
Expand Down Expand Up @@ -5273,7 +5303,10 @@ static const ARMCPRegInfo v8_cp_reginfo[] = {
{ .name = "CURRENTEL", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .opc2 = 2, .crn = 4, .crm = 2,
.access = PL1_R, .type = ARM_CP_CURRENTEL },
/* Cache ops: all NOPs since we don't emulate caches */
/*
* Instruction cache ops. All of these except `IC IVAU` NOP because we
* don't emulate caches.
*/
{ .name = "IC_IALLUIS", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 1, .opc2 = 0,
.access = PL1_W, .type = ARM_CP_NOP,
Expand All @@ -5286,9 +5319,17 @@ static const ARMCPRegInfo v8_cp_reginfo[] = {
.accessfn = access_tocu },
{ .name = "IC_IVAU", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 3, .crn = 7, .crm = 5, .opc2 = 1,
.access = PL0_W, .type = ARM_CP_NOP,
.access = PL0_W,
.fgt = FGT_ICIVAU,
.accessfn = access_tocu },
.accessfn = access_tocu,
#ifdef CONFIG_USER_ONLY
.type = ARM_CP_NO_RAW,
.writefn = ic_ivau_write
#else
.type = ARM_CP_NOP
#endif
},
/* Cache ops: all NOPs since we don't emulate caches */
{ .name = "DC_IVAC", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 6, .opc2 = 1,
.access = PL1_W, .accessfn = aa64_cacheop_poc_access,
Expand Down

0 comments on commit 9719f12

Please sign in to comment.