Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/boards/native_sim.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONFIG_ZEPHYR_POSIX=y
CONFIG_SYS_HEAP_BIG_ONLY=y
CONFIG_ZEPHYR_NATIVE_DRIVERS=y
CONFIG_ZEPHYR_LOG=y
112 changes: 82 additions & 30 deletions scripts/sof-qemu-run.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ def main():
parser = argparse.ArgumentParser(description="Run QEMU via west and automatically decode crashes.")
parser.add_argument("--build-dir", default="build", help="Path to the build directory containing zephyr.elf, linker.cmd, etc. Defaults to 'build'.")
parser.add_argument("--log-file", default="qemu-run.log", help="Path to save the QEMU output log. Defaults to 'qemu-run.log'.")
parser.add_argument("--valgrind", action="store_true", help="Run the executable under Valgrind (only valid for native_sim).")
args = parser.parse_args()

# Make absolute path just in case
# The shell script cd's into `args.build_dir` before executing us, so `args.build_dir` might be relative to the shell script's pwd.
# We resolve it relative to the python script's original invocation cwd.
build_dir = os.path.abspath(args.build_dir)

Comment thread
lgirdwood marked this conversation as resolved.
print(f"Starting QEMU test runner. Monitoring for crashes (Build Dir: {args.build_dir})...")
Expand All @@ -91,7 +94,53 @@ def main():
print("Please ensure you have sourced the Zephyr environment (e.g., source zephyr-env.sh).")
sys.exit(1)

child = pexpect.spawn(west_path, ["-v", "build", "-t", "run"], encoding='utf-8')
# Detect the board configuration from CMakeCache.txt
is_native_sim = False

cmake_cache = os.path.join(build_dir, "CMakeCache.txt")

if os.path.isfile(cmake_cache):
with open(cmake_cache, "r") as f:
for line in f:
if line.startswith("CACHED_BOARD:STRING=") or line.startswith("BOARD:STRING="):
if "native_sim" in line.split("=", 1)[1].strip():
is_native_sim = True
break

# Determine execution command
# If the user is running the python script directly from outside the workspace, we need to provide the source directory.
# But if west finds it automatically (or we are in the build dir), providing `-s` might clear the CACHED_BOARD config.
run_cmd = [west_path, "-v", "build", "-d", build_dir]

# Check if we are physically sitting inside the build directory
if os.path.abspath(".") != os.path.abspath(build_dir):
# We need to explicitly supply the app source to prevent west from crashing
app_source_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "app"))
run_cmd.extend(["-s", app_source_dir])

run_cmd.extend(["-t", "run"])

if args.valgrind:
if not is_native_sim:
print("[sof-qemu-run] Error: --valgrind is only supported for the native_sim board.")
sys.exit(1)

print("[sof-qemu-run] Rebuilding before valgrind...")
subprocess.run([west_path, "build", "-d", build_dir], check=True)

valgrind_path = shutil.which("valgrind")
if not valgrind_path:
print("[sof-qemu-run] Error: 'valgrind' command not found in PATH.")
sys.exit(1)

exe_path = os.path.join(build_dir, "zephyr", "zephyr.exe")
if not os.path.isfile(exe_path):
print(f"[sof-qemu-run] Error: Executable not found at {exe_path}")
sys.exit(1)

run_cmd = [valgrind_path, exe_path]

child = pexpect.spawn(run_cmd[0], run_cmd[1:], encoding='utf-8')

# We will accumulate output to check for crashes
full_output = ""
Expand Down Expand Up @@ -157,36 +206,39 @@ def main():

run_sof_crash_decode(build_dir, full_output)
else:
print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...")

# We need to send Ctrl-A c to enter the monitor
if child.isalive():
child.send("\x01c") # Ctrl-A c
try:
# Wait for (qemu) prompt
child.expect(r"\(qemu\)", timeout=5)
# Send "info registers"
child.sendline("info registers")
# Wait for the next prompt
child.expect(r"\(qemu\)", timeout=5)

info_regs_output = child.before
print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n")

# Quit qemu safely
child.sendline("quit")
child.expect(pexpect.EOF, timeout=2)
child.close()

# Run the decoder on the intercepted register output
run_sof_crash_decode(build_dir, info_regs_output)
except pexpect.TIMEOUT:
print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?")
child.close(force=True)
except pexpect.EOF:
print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.")
if is_native_sim:
print("\n[sof-qemu-run] No crash detected. (Skipping QEMU monitor interaction for native_sim)")
else:
print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.")
print("\n[sof-qemu-run] No crash detected. Interacting with QEMU Monitor to grab registers...")

# We need to send Ctrl-A c to enter the monitor
if child.isalive():
child.send("\x01c") # Ctrl-A c
try:
# Wait for (qemu) prompt
child.expect(r"\(qemu\)", timeout=5)
# Send "info registers"
child.sendline("info registers")
# Wait for the next prompt
child.expect(r"\(qemu\)", timeout=5)

info_regs_output = child.before
print("\n[sof-qemu-run] Successfully extracted registers from QEMU monitor.\n")

# Quit qemu safely
child.sendline("quit")
child.expect(pexpect.EOF, timeout=2)
child.close()

# Run the decoder on the intercepted register output
run_sof_crash_decode(build_dir, info_regs_output)
except pexpect.TIMEOUT:
print("\n[sof-qemu-run] Timed out waiting for QEMU monitor. Is it running?")
child.close(force=True)
except pexpect.EOF:
print("\n[sof-qemu-run] QEMU terminated before we could run monitor commands.")
else:
print("\n[sof-qemu-run] Process is no longer alive, cannot extract registers.")

Comment thread
lgirdwood marked this conversation as resolved.
if __name__ == "__main__":
main()
55 changes: 44 additions & 11 deletions scripts/sof-qemu-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,60 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2026 Intel Corporation. All rights reserved.

# Define the build directory from the first argument (or default)
BUILD_DIR="${1:-build}"
# Get the directory of this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")"

TARGET="native_sim"
VALGRIND_ARG=""

usage() {
cat <<EOF
Usage: $0 [OPTIONS] [TARGET]

Options:
-h, --help Show this help message and exit
--valgrind Run under valgrind

TARGET The QEMU target name (e.g., native_sim, qemu_xtensa, qemu_xtensa_mmu).
The build directory will be resolved to 'build-<TARGET>' relative
to the workspace. (default: native_sim)
EOF
exit 0
}

while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
--valgrind)
VALGRIND_ARG="--valgrind"
;;
*)
# Allow users who pass the directory name directly out of habit to still work
if [[ "$1" == build-* ]]; then
TARGET="${1#build-}"
else
TARGET="$1"
fi
;;
esac
shift
done

BUILD_DIR="${SOF_WORKSPACE}/build-${TARGET}"

# Find and source the zephyr environment script, typically via the sof-venv wrapper
# or directly if running in a known zephyrproject layout.
# We will use the existing helper sof-venv.sh to get the right environment.

# Get the directory of this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOF_WORKSPACE="$(dirname "$(dirname "$SCRIPT_DIR")")"

# Use the SOF workspace to locate the virtual environment
VENV_DIR="$SOF_WORKSPACE/.venv"
echo "Using SOF environment at $SOF_WORKSPACE"

# start the virtual environment
source ${VENV_DIR}/bin/activate

# Execute the QEMU runner from within the correct build directory
cd "${BUILD_DIR}" || exit 1

# Finally run the python script which will now correctly inherit 'west' from the sourced environment.
python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}"

python3 "${SCRIPT_DIR}/sof-qemu-run.py" --build-dir "${BUILD_DIR}" $VALGRIND_ARG
Comment thread
lgirdwood marked this conversation as resolved.
4 changes: 4 additions & 0 deletions scripts/xtensa-build-zephyr.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ class PlatformConfig:
"zephyr", "qemu_xtensa/dc233c/mmu",
"", "", "zephyr"
),
"native_sim" : PlatformConfig(
"zephyr", "native_sim",
"", "", "zephyr"
),
}

platform_configs = platform_configs_all.copy()
Expand Down
6 changes: 6 additions & 0 deletions src/platform/posix/ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ SOF_DEFINE_REG_UUID(ipc_task_posix);

static struct ipc *global_ipc;

#ifdef CONFIG_ARCH_POSIX_LIBFUZZER
// Not an ISR, called from the native_posix fuzz interrupt. Left
// alone for general hygiene. This is how a IPC interrupt would look
// if we had one.
Comment on lines +17 to 20
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the #ifdef CONFIG_ARCH_POSIX_LIBFUZZER block, the extern declaration for posix_fuzz_buf/posix_fuzz_sz (currently extern uint8_t *posix_fuzz_buf, posix_fuzz_sz;) does not match the definitions in src/platform/posix/fuzz.c (const uint8_t *posix_fuzz_buf; size_t posix_fuzz_sz;). This mismatch can cause incorrect reads/writes (e.g., posix_fuzz_sz = 0; only updating 1 byte). Please split these into separate externs with the correct types (and const).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

under investigation why we dont use header to align.

Expand Down Expand Up @@ -131,6 +132,7 @@ static void fuzz_isr(const void *arg)

posix_ipc_isr(NULL);
}
#endif

// This API is... confounded by its history. With IPC3, the job of
// this function is to get a newly-received IPC message header (!)
Expand Down Expand Up @@ -172,12 +174,14 @@ int ipc_platform_compact_read_msg(struct ipc_cmd_hdr *hdr, int words)
// Re-raise the interrupt if there's still fuzz data to process
void ipc_platform_complete_cmd(struct ipc *ipc)
{
#ifdef CONFIG_ARCH_POSIX_LIBFUZZER
extern void posix_sw_set_pending_IRQ(unsigned int IRQn);

if (fuzz_in_sz > 0) {
posix_fuzz_sz = 0;
posix_sw_set_pending_IRQ(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ);
}
#endif
}

int ipc_platform_send_msg(const struct ipc_msg *msg)
Expand All @@ -200,8 +204,10 @@ void ipc_platform_send_msg_direct(const struct ipc_msg *msg)

int platform_ipc_init(struct ipc *ipc)
{
#ifdef CONFIG_ARCH_POSIX_LIBFUZZER
IRQ_CONNECT(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ, 0, fuzz_isr, NULL, 0);
irq_enable(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ);
#endif

global_ipc = ipc;
schedule_task_init_edf(&ipc->ipc_task, SOF_UUID(ipc_task_posix_uuid),
Expand Down
3 changes: 3 additions & 0 deletions zephyr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@ zephyr_library_sources_ifdef(CONFIG_ZEPHYR_POSIX
${SOF_PLATFORM_PATH}/posix/dai.c
${SOF_PLATFORM_PATH}/posix/ipc.c
${SOF_PLATFORM_PATH}/posix/posix.c
)

zephyr_library_sources_ifdef(CONFIG_ARCH_POSIX_LIBFUZZER
${SOF_PLATFORM_PATH}/posix/fuzz.c
)

Expand Down
Loading