grumpycoders / grumpycoders/pcsx-redux

Initialising usable memory via `swr` & `swl` insn causes MSAN to falsely report uninitialised read

Open
#1,928 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
994
Forks
151
Avg merge
1d 15h
Merged PRs (30d)
18

Description

### Describe the bug

Allocating heap for an object of any size and then attempting to initialise any part of the memory using the `swr , ()` or `swl , ()` instructions causes MSAN to falsely report that an uninitialised read is occurring.

This is due to the emulator implementing `swr` ([DynaRecCPU::recSWR](https://github.com/grumpycoders/pcsx-redux/blob/main/src/core/DynaRec_aa64/instructions.cc#L1432-L1513)) and `swl` ([DynaRecCPU::recSWL](https://github.com/grumpycoders/pcsx-redux/blob/main/src/core/DynaRec_aa64/instructions.cc#L1351-L1430)) as several micro-ops starting with a read at the aligned address, modifying the lower (`swr`) or upper (`swl`) 1-4 bytes, then writing back to the aligned address.

The initial aligned read is performed via [PCSX::Memory::read32](https://github.com/grumpycoders/pcsx-redux/blob/dec9cd9e003e55a55089a9a7a78f45616c741e34/src/core/psxmem.cc#L374) (maybe other method depending on the read size, 32, 16, 8 bits respectively) which issues a call to [PCSX::Memory::msanGetStatus](https://github.com/grumpycoders/pcsx-redux/blob/dec9cd9e003e55a55089a9a7a78f45616c741e34/src/core/psxmem.h#L95) and thus detects an uninitialised read, invoking a breakpoint + console write.

![Image](https://github.com/user-attachments/assets/658b0549-94ef-4ff8-a600-58a3a7209174)

### Expected behavior

Ideally, this should either mask out the bytes being written to when checking initialisation of memory, but still attempt to validate the usability of the memory. Thus, no uninitialised read will be triggered for this circumstance.

This should tie in with a successive access via `lw/lwr/lwl`, the desired MSAN outcomes are described in the example below in comments.

### Steps to reproduce the bug

Minimal reproducible example:
```c
#include
#include

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"

void pcsx_initMsan() { *((volatile char* const)0x1f802089) = 0; }

void* pcsx_msanAlloc(size_t size) {
register uint32_t a0 asm("a0") = (uint32_t) size;
return *((void* volatile* const)0x1f80208c);
}

void pcsx_msanFree(void* ptr) { *((void* volatile* const)0x1f80208c) = ptr; }

#pragma GCC diagnostic pop

int main() {
pcsx_initMsan();
uint32_t* array = pcsx_msanAlloc(sizeof(uint32_t));
int byte_value = '\0';
asm ("swr %1, 0(%0)"
:
: "r" (array), "r" (byte_value));
pcsx_msanFree(array);
return 0;
}
```

Extended example with labelled desired outcomes:
```c
#include
#include
#include

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"

void pcsx_initMsan() { *((volatile char* const)0x1f802089) = 0; }

void* pcsx_msanAlloc(size_t size) {
register uint32_t a0 asm("a0") = (uint32_t) size;
return *((void* volatile* const)0x1f80208c);
}

void pcsx_msanFree(void* ptr) { *((void* volatile* const)0x1f80208c) = ptr; }

#pragma GCC diagnostic pop

int main() {
pcsx_initMsan();
register int32_t* array = pcsx_msanAlloc(sizeof(int32_t));
register int32_t byte_value = 0x11223344;
// Should succeed
asm ("swl %0, 1(%1)"
:
: "r" (byte_value), "r" (array));
printf("SWL suceeded\n");
register volatile int32_t result = 0;
// Should fail
asm ("lwr %0, 0(%1)"
: "+r"(result)
: "r"(array));
printf("LWR 0: 0x%x\n", result);
result = 0;
// Should fail
asm ("lwr %0, 1(%1)"
: "+r"(result)
: "r"(array));
printf("LWR 1: 0x%x\n", result);
result = 0;
// Should fail
asm ("lwr %0, 2(%1)"
: "+r"(result)
: "r"(array));
printf("LWR 2: 0x%x\n", result);
result = 0;
// Should fail
asm ("lwr %0, 3(%1)"
: "+r"(result)
: "r"(array));
printf("LWR 3: 0x%x\n", result);
result = 0;
// Should succeed
asm ("lwl %0, 0(%1)"
: "+r"(result)
: "r"(array));
printf("LWL 0: 0x%x\n", result);
result = 0;
// Should succeed
asm ("lwl %0, 1(%1)"
: "+r"(result)
: "r"(array));
printf("LWL 1: 0x%x\n", result);
result = 0;
// Should fail
asm ("lwl %0, 2(%1)"
: "+r"(result)
: "r"(array));
printf("LWL 2: 0x%x\n", result);
result = 0;
// Should fail
asm ("lwl %0, 3(%1)"
: "+r"(result)
: "r"(array));
printf("LWL 3: 0x%x\n", result);
pcsx_msanFree(array);
return 0;
}
```

### Operating System

MacOS Sequoia

### PCSX-Redux version

Version: dec9cd9e
Build: 151
Changeset: dec9cd9e003e55a55089a9a7a78f45616c741e34
Date & time: 2025-04-18 08:19:57

### CPU model

Apple M4 Pro

### GPU model & Drivers

Apple M4 Pro, OpenGL 4.1, Metal 89.4

### BIOS version

OpenBIOS

### Options

- [x] Dynarec CPU
- [ ] 8MB
- [ ] OpenGL GPU
- [ ] Fastboot
- [ ] Debugger

### Iso checks

_No response_

### Logs

Minimal reproducible example:

>

```
Loaded BIOS: /Applications/PCSX-Redux.app/Contents/MacOS/../Resources/share/pcsx-redux/resources/openbios.bin
OpenBIOS detected (69be4aa4)
Loaded CD Image: /Users/jackkilrain/projects/C-CPP/PSX-Minecraft/build/PSXMC.bin[+cue].
Track 01 (DATA) - Start 00:02:00, Length 00:00:54
CD-ROM Label: PSXMC
CD-ROM ID:
CD-ROM EXE Name: PSXMC.exe
================
uC-sdk - booting
================
BIOS Trace1: 0x0f
BIOS Trace1: 0x0e
BIOS Trace1: 0x01
BIOS Trace1: 0x02
BIOS Trace1: 0x03
BIOS Trace1: 0x04
BIOS Trace1: 0x01
BIOS Trace1: 0x03
BIOS Trace1: 0x04
BIOS Trace1: 0x05
BIOS Trace1: 0x06
BIOS Trace1: 0x02
BIOS Trace1: 0x05
PS-X Realtime Kernel OpenBios - build id d9a6e07696256e0f7b0ac3e9b261e53cfc32e648.
Copyright (C) 2019-2025 PCSX-Redux authors.
BIOS Trace1: 0x06
KERNEL SETUP!
Configuration : EvCB 0x10 TCB 0x04
BIOS Trace1: 0x07
*** OpenBIOS tiny shell - starting ***
*** Data is acceptable, booting now. ***
BIOS Trace1: 0x08
BOOTSTRAP LOADER
BIOS Trace1: 0x09
setup file : cdrom:SYSTEM.CNF;1
TCB 00000004
EVENT 00000010
STACK 801ffff0
BOOT = cdrom:\PSXMC.exe
argument =
KERNEL SETUP!
Configuration : EvCB 0x10 TCB 0x04
boot file : cdrom:\PSXMC.exe
EXEC:PC0(80010118) T_ADDR(80010000) T_SIZE(00001800)
boot address : 80010118 801ffff0
Execute !
S_ADDR(801ffff0) S_SIZE(00000000)
c: Initialised TLSF allocator
32-bit read from usable but uninitialized msan memory: 20000400
```

### Additional information

SDKs like PSn00bSDK, or just regular user implementation of inline ASM, optimisations by the compiler, etc will most likely run in to this. In this case, the `memset` implementation in PSn00bSDK uses a modified Duff's machine to do batched writes over large address spaces, using `swr` to fill the alignment correctly before issuing batched stores.

In one of the original PRs that implemented MSAN, it was noted that this might need to be handled: https://github.com/grumpycoders/pcsx-redux/pull/1866#discussion_r1970918628 and that there was an approach that seems to line up with the masking idea presented here: https://github.com/grumpycoders/pcsx-redux/pull/1866#discussion_r1972523716.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.