Skip to content

Pr2349 rebased - #2375

Open
wtdcode wants to merge 24 commits into
devfrom
pr2349-rebased
Open

Pr2349 rebased#2375
wtdcode wants to merge 24 commits into
devfrom
pr2349-rebased

Conversation

@wtdcode

@wtdcode wtdcode commented Jul 28, 2026

Copy link
Copy Markdown
Member

#2349 Upgrade our QEMU fork to 7.1.22.

We will finally chase up to the upstream QEMU.

Nitr0-G and others added 21 commits July 28, 2026 09:04
Update the embedded QEMU-derived runtime and target code to the 7.2.22 baseline while preserving Unicorn's reduced integration model.

This brings in the QEMU 7.2 support code needed by the fork: softfloat/int128 and helper compatibility, TCG and translator glue, updated symbol-postfix headers, target feature masks, and generated binding constants.

CPU-visible coverage was expanded across the supported targets: AArch64 SVE/SME/MTE/PMU and M-profile MVE, RISC-V RVV/RVH/bitmanip/crypto/Zfh/Sstc/PMP paths, s390x MIE/VE/Vector-FP/string/crypto paths, PPC POWER10 groups, MIPS public modes and MSA registers, M68K FPU/MSP/ColdFire behavior, SPARC register access, and x86 AVX/AVX2/FMA/BMI/VAES/VPCLMUL validation.

The unit test suite was extended with focused regressions for the migrated behavior, and the local MSVC test tree has been validated with full parallel CTest.
Keep the aarch64 backend call emitter on the reduced tree's tcg_out_call pointer ABI so ubuntu-aarch64 builds compile again.

Use concise CTest failure output in Build UC2 so parallel CI logs expose the failing test case instead of losing it inside verbose interleaved output.
Align host TCG direct jump patching with the QEMU 7.2 tc_ptr/jmp_rx/jmp_rw ABI, add missing non-goto_ptr host backend definitions, and fix PPC host TCG opcode/table drift.

Move variable target page size state out of release-mode macros that depended on a local uc variable, using arch-postfixed target_page_bits_state instead.

Fix POSIX MIPS CPU alignment and preserve microMIPS entry state, and avoid Apple JIT state asserts when virtualized macOS runners cannot report SPRR permissions.
Keep variable target page size state per Unicorn engine instead of arch-global state, while preserving the improved finalization path.

Add a public control regression that runs two ARM engines with different page sizes in the same process.
Synchronize generated code writes with instruction fetches when patching TCG direct jumps and finalizing generated code. This keeps the reduced JIT path aligned with QEMU 7.2 host cache semantics on non-coherent host caches.
Align the reduced TCG generated-code pointers with the QEMU 7.2 RX/RW address model so host backends compute branch offsets, labels, prologue addresses, and jump patches against executable addresses while still writing through writable addresses.

Use runtime MIPS guest endianness for unaligned store helpers and declare the PPC host cache flush helper used by reduced per-target builds.
Keep split-WX conversion inline in the reduced single-mapping TCG runtime so per-arch archives do not define duplicate data symbols during GNU ld links.
Pin the Zig macOS workflow to macos-14 so Zig 0.14 does not run on an unsupported macOS 26 image.
Use pthread JIT write-protection transitions directly and avoid private SPRR state probes on Apple Silicon hosts.
Use helper typemasks when extending TCG call arguments so pointer operands are not treated as 32-bit values on aarch64 hosts. Restore s390x instruction-start metadata emission for early Unicorn exit TBs.
Avoid freeing BF16 VCVT temporaries after neon_store_reg consumes them.
Restore QEMU 7.2 ppc modulo lowering and long goto_tb reset handling.

Revalidate TLB entries after Unicorn memory callbacks that can flush or resize TLB state.
QEMU relies on signed arithmetic wrapping in a number of helpers. For
example the MVE DO_ABD macro computes (M) - (N) on extremal int32_t
operands, which overflows and is undefined behaviour.

clang exploits that UB and returns the negated result, which made
test_arm_m55_mve_vabd fail on the macOS runners while gcc happened to
emit the wrapping subtraction and passed. Upstream QEMU's configure
passes -fwrapv for the same reason, so do the same here.
@unknown-1-0

unknown-1-0 commented Jul 28, 2026

Copy link
Copy Markdown

Hi, @wtdcode and @Nitr0-G, thank you for the great work on this update.

I noticed that this PR fixes the same issue as #2370 (ran the tests from that PR, they pass). Should that PR be closed? And should the tests in it be added as a separate PR?

However, I also noticed that CONFIG_USER_ONLY defines are now used in the qemu/target/i386/ (specifically, in helper-tcg.h, fpu_helper.c and translate.c). Was it intentional to keep them (like in qemu/target/s390x and qemu/include/hw/ppc/ppc.h)? Or are they simply leftovers from QEMU that should be removed?

@wtdcode

wtdcode commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Hi @unknown-1-0

Thanks for your interest and for discovering our "secret" works =). This branch is currently not ready for merging and is just for testing and interactive review. But I do expect this to be included in 2.2.0, the next release.

@@ -0,0 +1,62 @@
/*

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.

No symlinks.

RAMBlock *block;
ram_addr_t offset;

host = allocation_tag_probe_access(env, clean_ptr, access_type, size,

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.

This seems different from https://github.com/qemu/qemu/blob/v7.2.22/target/arm/mte_helper.c#L121

Looks like you forget to port CPUTLBEntryFull? Is that intended? Or are you sure the semantic is equivalent?

tcg_ctx->cpu = NULL;

/* generate machine code */
tb->jmp_reset_offset[0] = TB_JMP_RESET_OFFSET_INVALID;

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.

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.

@Nitr0-G How about this one?

@wtdcode

wtdcode commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

I used Claude to semi-manually review this PR and the above findings should be the most relevant. I will fix the others later.

@Nitr0-G

@wtdcode

wtdcode commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

@PhilippTakacs Let me know if this port breaks your workflows.

@PhilippTakacs

Copy link
Copy Markdown
Contributor

I have run some tests and get a NULL deref in qemu/hw/core/cpu.c:118:

static void cpu_legacy_unaligned_access(CPUState *cpu, vaddr addr,
                                        MMUAccessType access_type,
                                        int mmu_idx, uintptr_t retaddr)
{
    CPUClass *cc = CPU_GET_CLASS(cpu);

    cc->do_unaligned_access(cpu, addr, access_type, mmu_idx, retaddr);
}

The do_unaligned_access is NULL and some other function pointer in this struct also:

Process 13225 stopped
* thread #1, name = 'mogi_run_progra', stop reason = step in
    frame #0: 0x0000555555f9acb2 mogi_run_program_unicorn`cpu_legacy_unaligned_access(cpu=0x000055555747cf90, addr=140737488289272, access_type=MMU_DATA_STORE, mmu_idx=2, retaddr=140736274136276) at cpu.c:118:7
   115 	{
   116 	    CPUClass *cc = CPU_GET_CLASS(cpu);
   117 	
-> 118 	    cc->do_unaligned_access(cpu, addr, access_type, mmu_idx, retaddr);
   119 	}
   120 	
   121 	static const struct TCGCPUOps cpu_legacy_tcg_ops = {
(lldb) p cc
(CPUClass *) 0x0000555557489a50
(lldb) p cc->do_unaligned_access
(void (*)(CPUState *, vaddr, MMUAccessType, int, uintptr_t)) 0x0000000000000000
(lldb) p *cc
(CPUClass) {
  reset = 0x0000555556045e8f (mogi_run_program_unicorn`x86_cpu_reset at cpu.c:4569:1)
  has_work = 0x0000555556047160 (mogi_run_program_unicorn`x86_cpu_has_work at cpu.c:5115:1)
  do_interrupt = 0x000055555603b261 (mogi_run_program_unicorn`x86_cpu_do_interrupt_x86_64 at seg_helper.c:1205:1)
  do_unaligned_access = 0x0000000000000000
  get_arch_id = 0x0000555556046f3d (mogi_run_program_unicorn`x86_cpu_get_arch_id at cpu.c:5048:1)
  get_paging_enabled = 0x0000555556046f5f (mogi_run_program_unicorn`x86_cpu_get_paging_enabled at cpu.c:5055:1)
  get_memory_mapping = 0x00005555560779ab (mogi_run_program_unicorn`x86_cpu_get_memory_mapping_x86_64 at arch_memory_mapping.c:302:1)
  set_pc = 0x0000555556046f8c (mogi_run_program_unicorn`x86_cpu_set_pc at cpu.c:5062:1)
  synchronize_from_tb = 0x0000555556046fb6 (mogi_run_program_unicorn`x86_cpu_synchronize_from_tb at cpu.c:5069:1)
  tlb_fill = 0x0000555556096f14 (mogi_run_program_unicorn`unicorn_fill_tlb_x86_64 at unicorn_vtlb.c:62:1)
  tlb_fill_cpu = 0x00005555560419d4 (mogi_run_program_unicorn`x86_cpu_tlb_fill_x86_64 at excp_helper.c:678:1)
  get_phys_page_debug = 0x0000000000000000
  get_phys_page_attrs_debug = 0x0000555556032eb8 (mogi_run_program_unicorn`x86_cpu_get_phys_page_attrs_debug_x86_64 at helper.c:221:1)
  asidx_from_attrs = 0x0000555556042a6a (mogi_run_program_unicorn`x86_asidx_from_attrs at cpu.h:2009:1)
  debug_check_watchpoint = 0x0000000000000000
  debug_excp_handler = 0x0000555556042349 (mogi_run_program_unicorn`breakpoint_handler_x86_64 at bpt_helper.c:209:1)
  cpu_exec_enter = 0x0000555556033499 (mogi_run_program_unicorn`x86_cpu_exec_enter_x86_64 at helper.c:400:1)
  cpu_exec_exit = 0x000055555603352e (mogi_run_program_unicorn`x86_cpu_exec_exit_x86_64 at helper.c:411:1)
  cpu_exec_interrupt = 0x000055555603b391 (mogi_run_program_unicorn`x86_cpu_exec_interrupt_x86_64 at seg_helper.c:1228:1)
  tcg_ops = 0x0000555557275f40
  adjust_watchpoint_address = 0x0000000000000000
  tcg_initialize = 0x000055555601133b (mogi_run_program_unicorn`tcg_x86_init_x86_64 at translate.c:7111:1)
}
(lldb) bt
* thread #1, name = 'mogi_run_progra', stop reason = step in
  * frame #0: 0x0000555555f9acb2 mogi_run_program_unicorn`cpu_legacy_unaligned_access(cpu=0x000055555747cf90, addr=140737488289272, access_type=MMU_DATA_STORE, mmu_idx=2, retaddr=140736274136276) at cpu.c:118:7
    frame #1: 0x0000555555fb3ce2 mogi_run_program_unicorn`cpu_tcg_unaligned_access(cpu=0x000055555747cf90, addr=140737488289272, access_type=MMU_DATA_STORE, mmu_idx=2, retaddr=140736274136276) at tcg-cpu-ops.h:96:9
    frame #2: 0x0000555555fb8297 mogi_run_program_unicorn`store_helper(env=0x00005555574859e0, addr=140737488289272, val=0, oi=2098, retaddr=140736274136276, op=MO_64) at cputlb.c:2247:9
    frame #3: 0x0000555555fb9394 mogi_run_program_unicorn`helper_le_stq_mmu_x86_64(env=0x00005555574859e0, addr=140737488289272, val=0, oi=2098, retaddr=140736274136276) at cputlb.c:2541:5
    frame #4: 0x00007fffb7a07cd4
(lldb) 

@Nitr0-G

Nitr0-G commented Jul 30, 2026

Copy link
Copy Markdown

I have run some tests and get a NULL deref in qemu/hw/core/cpu.c:118:

I found this out tonight too, can you check #2377 please?

I think I fixed this problem and some other shit too

@PhilippTakacs

Copy link
Copy Markdown
Contributor

With #2377 my test reaches handle_unaligned_access which just calls raise_exception_ra and the emulation stops with UC_ERR_EXCEPTION. The problem is this code works perfect fine on the current dev branch. I'm working on a PoC.

@Nitr0-G

Nitr0-G commented Jul 30, 2026

Copy link
Copy Markdown

With #2377 my test reaches handle_unaligned_access which just calls raise_exception_ra and the emulation stops with UC_ERR_EXCEPTION. The problem is this code works perfect fine on the current dev branch. I'm working on a PoC.

Yes - qemu/qemu@958e1dd
With old qemu, Unicorn ignored some things that needed to be leveled. I'd be happy to see a PoC. If it really is a bug, I'll fix it as soon as possible.

* port QEMU 7.2 TLB and TB maintenance

Port full TLB entries and preserve translated ARM page attributes for MTE and BTI checks.

Move translation block cache, linking, invalidation, and flush maintenance into tb-maint.c while keeping Unicorn per-engine state and SMC behavior.

Add guarded-page, self-linked TB, invalidation, and engine-isolation coverage.

* fix QEMU 7.2.22 partial ports
@PhilippTakacs

Copy link
Copy Markdown
Contributor

I found the problem, I had a not correct aligned the stack pointer.

@PhilippTakacs

Copy link
Copy Markdown
Contributor

I'm currently working on some improvements related to the tlb (TLB_NOTDIRTY, call all memory hooks, ...). I wounder if I it's better to target this branch to avoid implementing it two times.

@Nitr0-G

Nitr0-G commented Jul 31, 2026

Copy link
Copy Markdown

I'm currently working on some improvements related to the tlb (TLB_NOTDIRTY, call all memory hooks, ...). I wounder if I it's better to target this branch to avoid implementing it two times.

I think yes. @wtdcode

@PhilippTakacs

Copy link
Copy Markdown
Contributor

I have run some tests with our fuzzer/emulator (https://github.com/mogikai/mogi) and found a ~10% time lost using this branch only by running the 3mm benchmark without fuzzig. A quick look at the perf data shows most time is lost in the load_help. I assume either the fast load (avoiding the load_helper) is less used, the tlb is smaller, or the load_helper is significant slower.

@Nitr0-G

Nitr0-G commented Aug 3, 2026

Copy link
Copy Markdown

I have run some tests with our fuzzer/emulator (https://github.com/mogikai/mogi) and found a ~10% time lost using this branch only by running the 3mm benchmark without fuzzig. A quick look at the perf data shows most time is lost in the load_help. I assume either the fast load (avoiding the load_helper) is less used, the tlb is smaller, or the load_helper is significant slower.

I'll test this and try to fix it! Thanks for telling!

There are a small set of binary SSE insns which have no MMX
equivalent, which we create the gen functions for with the
BINARY_INT_SSE() macro.  This forwards to gen_binary_int_sse() with a
NULL pointer for 'mmx'.

For almost all of these insns we correctly mark them in the decode
table as not permitting a zero prefix byte; however we got this wrong
for VPERMILPS, with the result that a bogus instruction would get
through the decode checks and end up in gen_binary_int_sse() trying
to call a NULL pointer.

Correct the decode table entry for VPERMILPS so that we get the
expected #UD exception.

In the x86 SDM, table A-4 "Three-byte Opcode Map: 08H-FFH
(First Two Bytes are 0F 38H)" confirms that there is no pfx 0
version of VPERMILPS.

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


Andrey Polivoda: Added a unit test
Upstream QEMU commit: ebd9ea2947d88f237e20333fe547ca8817d0b0ee

Signed-off-by: Andrey Polivoda <apolivodaa433@gmail.com>
…ll` (#2406)

The original variant of that test passed an incorrectly calculated size
parameter, which led to 0F 38 0C 00 being executed instead of 0F 38 0C FF.

This commit fixes that and explicitly tests both variants of the instruction.

Signed-off-by: Andrey Polivoda <apolivodaa433@gmail.com>
@Nitr0-G

Nitr0-G commented Aug 28, 2026

Copy link
Copy Markdown

I have run some tests with our fuzzer/emulator (https://github.com/mogikai/mogi) and found a ~10% time lost using this branch only by running the 3mm benchmark without fuzzig. A quick look at the perf data shows most time is lost in the load_help. I assume either the fast load (avoiding the load_helper) is less used, the tlb is smaller, or the load_helper is significant slower.

Hello! Sorry for bothering u, but can u retest it with pr2349-rebased branch in my fork (https://github.com/Nitr0-G/unicorn/tree/pr2349-rebased)? It seems i fixed it

@PhilippTakacs

Copy link
Copy Markdown
Contributor

this doesn't bother me at all. Shall I directly test against your pr2349-rebased-test-asan branch?

@Nitr0-G

Nitr0-G commented Aug 31, 2026

Copy link
Copy Markdown

this doesn't bother me at all. Shall I directly test against your pr2349-rebased-test-asan branch?

Directly test pr2349-rebased. pr2349-rebased-test-asan it's just a test branch for libfuzzer/asan and etc that work in ci\cd on pull requests. I was tired and accidentally made a mistake when I was doing the PR, and because of that I made it into this repository(#2409), and not my local fork, sorry

@Nitr0-G

Nitr0-G commented Aug 31, 2026

Copy link
Copy Markdown
image_2026-08-30_23-19-11

a small benchmark from my work

@PhilippTakacs

Copy link
Copy Markdown
Contributor

I have run the 3mm benchmark again and it looks pretty good: about 9 min 14 sec on dev and 1 min 45 sec on your pr2349-rebased branch.

@wtdcode

wtdcode commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Cool, then I'm going to get this merged soon.

@unknown-1-0

unknown-1-0 commented Sep 1, 2026

Copy link
Copy Markdown

Looks like the upstream introduced a new crash bug

diff --git a/tests/unit/test_x86.c b/tests/unit/test_x86.c
index ece99098..98409b61 100644
--- a/tests/unit/test_x86.c
+++ b/tests/unit/test_x86.c
@@ -3558,6 +3558,21 @@ static void test_x86_vpermilps_null_ptr_call(void)
     OK(uc_close(uc));
 }
 
+static void test_x86_pinsrw_assert_failure(void)
+{
+    char code[] = {
+        0xc5, 0xf8, 0xc4, 0x00, 0x00
+    };
+
+    uc_engine *uc = NULL;
+    uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code));
+
+    uc_assert_err(UC_ERR_INSN_INVALID,
+            uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0));
+
+    OK(uc_close(uc));
+}
+
 static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data)
 {
     uint64_t h = 0x00000000FEDCBA98;
@@ -4094,6 +4109,7 @@ TEST_LIST = {
     {"test_rex_x64", test_rex_x64},
     {"test_x86_ro_segfault", test_x86_ro_segfault},
     {"test_x86_vpermilps_null_ptr_call", test_x86_vpermilps_null_ptr_call},
+    {"test_x86_pinsrw_assert_failure", test_x86_pinsrw_assert_failure},
     {"test_x86_hook_insn_rdtsc", test_x86_hook_insn_rdtsc},
     {"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp},
     {"test_x86_hook_insn_wrmsr", test_x86_hook_insn_wrmsr},
$ ./test_x86 pinsrw
Test test_x86_pinsrw_assert_failure...          test_x86: .../qemu/target/i386/emit.c.inc:1578: gen_pinsr: Assertion `vec_len == 16' failed.
Aborted
$

This same test results in Invalid memory read (UC_ERR_READ_UNMAPPED) on the current dev branch

i3-6100 raises a SIGILL when executing this code natively.

Note: upstream QEMU is affected too

$ cat pinsrw_test.asm
bits 64

global _start
_start:
db 0xc5, 0xf8, 0xc4, 0x00, 0x00
int3
$ nasm -f elf64 pinsrw_test.asm
$ ld.lld -o pinsrw_test pinsrw_test.o
$ ./pinsrw_test
Illegal instruction
$ .../QEMU/build_asan/qemu-x86_64 --version
qemu-x86_64 version 11.1.50 (v11.1.0-563-gc173b6af44-dirty)
Copyright (c) 2003-2026 Fabrice Bellard and the QEMU Project developers
$ .../QEMU/build_asan/qemu-x86_64 ./pinsrw_test
qemu-x86_64: ../target/i386/tcg/emit.c.inc:2912: gen_pinsr: Assertion `vec_len == 16' failed.
Aborted
$

@unknown-1-0

Copy link
Copy Markdown

And another one:

diff --git a/tests/unit/test_x86.c b/tests/unit/test_x86.c
index 98409b61..ed7b7150 100644
--- a/tests/unit/test_x86.c
+++ b/tests/unit/test_x86.c
@@ -3573,6 +3573,38 @@ static void test_x86_pinsrw_assert_failure(void)
     OK(uc_close(uc));
 }
 
+static void test_x86_vroundss_assert_failure(void)
+{
+    char code[] = {
+        0xc4, 0xe3, 0x7d, 0x0a, 0xc0, 0x00
+    };
+
+    uc_engine *uc = NULL;
+    uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code));
+
+    // Not sure whether this should raise UC_ERR_INSN_INVALID
+    // i3-6100 executes it without SIGILL at least
+    OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0));
+
+    OK(uc_close(uc));
+}
+
+static void test_x86_vroundsd_assert_failure(void)
+{
+    char code[] = {
+        0xc4, 0xe3, 0x7d, 0x0b, 0xc0, 0x00
+    };
+
+    uc_engine *uc = NULL;
+    uc_common_setup(&uc, UC_ARCH_X86, UC_MODE_64, code, sizeof(code));
+
+    // Not sure whether this should raise UC_ERR_INSN_INVALID
+    // i3-6100 executes it without SIGILL at least
+    OK(uc_emu_start(uc, code_start, code_start + sizeof(code), 0, 0));
+
+    OK(uc_close(uc));
+}
+
 static bool test_x86_hook_insn_rdtsc_cb(uc_engine *uc, void *user_data)
 {
     uint64_t h = 0x00000000FEDCBA98;
@@ -4110,6 +4142,8 @@ TEST_LIST = {
     {"test_x86_ro_segfault", test_x86_ro_segfault},
     {"test_x86_vpermilps_null_ptr_call", test_x86_vpermilps_null_ptr_call},
     {"test_x86_pinsrw_assert_failure", test_x86_pinsrw_assert_failure},
+    {"test_x86_vroundss_assert_failure", test_x86_vroundss_assert_failure},
+    {"test_x86_vroundsd_assert_failure", test_x86_vroundsd_assert_failure},
     {"test_x86_hook_insn_rdtsc", test_x86_hook_insn_rdtsc},
     {"test_x86_hook_insn_rdtscp", test_x86_hook_insn_rdtscp},
     {"test_x86_hook_insn_wrmsr", test_x86_hook_insn_wrmsr},

On this PR:

$ ./test_x86 vround
Test test_x86_vroundss_assert_failure...        test_x86: .../unicorn-pr2349/qemu/target/i386/emit.c.inc:2412: gen_VROUNDSS: Assertion `!s->vex_l' failed.
  Test interrupted by SIGABRT.
Test test_x86_vroundsd_assert_failure...        test_x86: .../unicorn-pr2349/qemu/target/i386/emit.c.inc:2404: gen_VROUNDSD: Assertion `!s->vex_l' failed.
  Test interrupted by SIGABRT.
FAILED: 2 of 2 unit tests have failed.
$ 

On current dev:

$ ./test_x86 vround
Test test_x86_vroundss_assert_failure...        [ FAILED ]
  test_x86.c:1575: Check __err == UC_ERR_OK... failed
    Invalid instruction (UC_ERR_INSN_INVALID)
Test test_x86_vroundsd_assert_failure...        [ FAILED ]
  test_x86.c:1591: Check __err == UC_ERR_OK... failed
    Invalid instruction (UC_ERR_INSN_INVALID)
FAILED: 2 of 2 unit tests have failed.

Upstream QEMU is affected by this too:

$ ~/Projects/QEMU/build_asan/qemu-x86_64 --version         
qemu-x86_64 version 11.1.50 (v11.1.0-563-gc173b6af44-dirty)
Copyright (c) 2003-2026 Fabrice Bellard and the QEMU Project developers
$ for i in vrounds{s,d}; do echo $i; cat "${i}_test.asm"; nasm -f elf64 "${i}_test.asm"; ld.lld -o "${i}_test" "${i}_test.o"; echo "Host execution result:"; sh -c "./${i}_test"; echo "QEMU execution result:"; ~/Projects/QEMU/build_asan/qemu-x86_64 "./${i}_test"; echo; done
vroundss
bits 64

global _start
_start:
db 0xc4, 0xe3, 0x7d, 0x0a, 0xc0, 0x00
int3
Host execution result:
Trace/breakpoint trap
QEMU execution result:
qemu-x86_64: ../target/i386/tcg/emit.c.inc:4684: gen_VROUNDSS: Assertion `!s->vex_l' failed.

vroundsd
bits 64

global _start
_start:
db 0xc4, 0xe3, 0x7d, 0x0b, 0xc0, 0x00
int3
Host execution result:
Trace/breakpoint trap
QEMU execution result:
qemu-x86_64: ../target/i386/tcg/emit.c.inc:4677: gen_VROUNDSD: Assertion `!s->vex_l' failed.

$

@PhilippTakacs

Copy link
Copy Markdown
Contributor

I have looked at emit.c.inc and decode-new.c.inc there are many asserts which are reachable by guest code. Maybe the best way to handle this is replace all this asserts to a self provided macro which just creates an illegal instruction and returns. what do you think?

@unknown-1-0

unknown-1-0 commented Sep 1, 2026

Copy link
Copy Markdown

For VROUNDSS/VROUNDSD, the assertion removal may work (however, I did not test what changes in the hardware behavior when VEX.L = 1 is set, and haven't looked whether QEMU does not rely on s->vex_l being 0). Another possible solution is to reject VROUNDSS/VROUNDSD during the decoding and not when generator function is reached (however, I don't know how many processors do NOT raise #UD with these instructions; if it happens on lots of them, then that may be a bad thing to do)

For VPINSRW, the issue seems to be the decoder allowing it to be encoded with a VEX prefix with VEX.pp == 0 (which means none of 0x66, 0xF2, or 0xF3 prefixes is present).

The entry in the Intel SDM looks like this:

NP 0F C4 /r ib [1]
PINSRW mm, r32/m16, imm8
NOTES:
1. See note in Section 2.5, “Intel® AVX and Intel® SSE Instruction Exception Classification,” in the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 2A, and Section 25.25.3, “Exception Conditions of Legacy SIMD Instructions Operating on MMX
Registers,” in the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3B.
66 0F C4 /r ib
PINSRW xmm, r32/m16, imm8
VEX.128.66.0F.W0 C4 /r ib
VPINSRW xmm1, xmm2, r32/m16, imm8
EVEX.128.66.0F.WIG C4 /r ib
VPINSRW xmm1, xmm2, r32/m16, imm8

Chapter 3 of the Intel SDM says this:

3.1.1.2 Opcode Column in the Instruction Summary Table (Instructions with VEX prefix)
In the Instruction Summary Table, the Opcode column presents each instruction encoded using the VEX prefix in
following form (including the modR/M byte if applicable, the immediate byte if applicable):
VEX.[128,256].[66,F2,F3].0F/0F3A/0F38.[W0,W1] opcode [/r] [/ib,/is4]
• VEX — Indicates the presence of the VEX prefix is required. The VEX prefix can be encoded using the threebyte form (the first byte is C4H), or using the two-byte form (the first byte is C5H). The two-byte form of VEX
only applies to those instructions that do not require the following fields to be encoded: VEX.mmmmm, VEX.W,
VEX.X, VEX.B. Refer to Section 2.3 for more detail on the VEX prefix.
The encoding of various sub-fields of the VEX prefix is described using the following notations:
— 128,256: VEX.L field can be 0 (denoted by VEX.128, VEX.L0, or VEX.LZ) or 1 (denoted by VEX.256 or
VEX.L1). The VEX.L field can be encoded using either the 2-byte or 3-byte form of the VEX prefix. The
presence of the notation VEX.256 or VEX.128 in the opcode column should be interpreted as follows:
• If VEX.256 is present in the opcode column: The semantics of the instruction must be encoded with
VEX.L = 1. An attempt to encode this instruction with VEX.L= 0 can result in one of two situations: (a)
if VEX.128 version is defined, the processor will behave according to the defined VEX.128 behavior; (b)
an #UD occurs if there is no VEX.128 version defined.
• If VEX.128 is present in the opcode column but there is no VEX.256 version defined for the same
opcode byte: Two situations apply: (a) For VEX-encoded, 128-bit SIMD integer instructions, software
must encode the instruction with VEX.L = 0. The processor will treat the opcode byte encoded with
VEX.L= 1 by causing an #UD exception; (b) For VEX-encoded, 128-bit packed floating-point instructions, software must encode the instruction with VEX.L = 0. The processor will treat the opcode byte
encoded with VEX.L= 1 by causing an #UD exception (e.g., VMOVLPS).
— 66,F2,F3: The presence or absence of these values map to the VEX.pp field encodings. If absent, this
corresponds to VEX.pp=00B. If present, the corresponding VEX.pp value affects the “opcode” byte in the
same way as if a SIMD prefix (66H, F2H or F3H) does to the ensuing opcode byte. Thus a non-zero encoding
of VEX.pp may be considered as an implied 66H/F2H/F3H prefix. The VEX.pp field may be encoded using
either the 2-byte or 3-byte form of the VEX prefix.

— 0F,0F3A,0F38: The presence maps to a valid encoding of the VEX.mmmmm field. Only three encoded
values of VEX.mmmmm are defined as valid, corresponding to the escape byte sequence of 0FH, 0F3AH,
and 0F38H. The effect of a valid VEX.mmmmm encoding on the ensuing opcode byte is same as if the
corresponding escape byte sequence on the ensuing opcode byte for non-VEX encoded instructions. Thus a
valid encoding of VEX.mmmmm may be consider as an implies escape byte sequence of either 0FH, 0F3AH
or 0F38H. The VEX.mmmmm field must be encoded using the 3-byte form of VEX prefix.

So that means that VEX.pp should be 1 (since it maps to 0x66) when the VEX prefix is used with PINSRW.
This would require fixing the decoder table and/or the decoder itself.

For other asserts: Since Unicorn is a library, I think it would be a better idea to keep these (since they are placed for a reason) and make these crash the process on debug builds, and make uc_emu_start return UC_ERR_INTERNAL (or anything similar) on release builds.

@unknown-1-0

unknown-1-0 commented Sep 1, 2026

Copy link
Copy Markdown

Why the invalid opcode (#UD) exception should be raised during the decoding and not in gen_* functions: by the time that function is called, two gen_load() would already be called, which would create situations when a seemingly invalid instruction would raise an invalid memory access error instead of an invalid instruction error, since qemu_ld/st TCG ops could already be generated by gen_load(). (Similar thing happened in code that #2362 fixes)

@PhilippTakacs

Copy link
Copy Markdown
Contributor

To bad, I though this could be easy to fix by replace the assert() with a self provided macro.

For other asserts: Since Unicorn is a library, I think it would be a better idea to keep these (since they are placed for a reason) and make these crash the process on debug builds, and make uc_emu_start return UC_ERR_INTERNAL (or anything similar) on release builds.

Of course they are there for a reason and should not removed without a replacement. I would say replace them with a macro which causes uc_* to return UC_ERR_INTERNAL even in a debug build. Just keeping the assert might cause an abort() call without the possibility for the user to handle this error.

@unknown-1-0

unknown-1-0 commented Sep 1, 2026

Copy link
Copy Markdown

Of course they are there for a reason and should not removed without a replacement. I would say replace them with a macro which causes uc_* to return UC_ERR_INTERNAL even in a debug build. Just keeping the assert might cause an abort() call without the possibility for the user to handle this error.

That makes sense. I think we can also let the user decide what should happen when an assertion failure occurs (via uc_ctl() maybe), since making the program abort when an assertion fails may be useful when debugging/fuzzing the Unicorn itself (however the latter use case may simply call abort() on the fuzzer side when UC_ERR_INTERNAL is returned). And we can make that UC_ERR_INTERNAL should be returned by default.

@unknown-1-0

Copy link
Copy Markdown

Intel Xeon Platinum 8370C (from 2-core instance of GitHub Codespaces) seems to ignore VEX.L with VROUNDSS/VROUNDSD

$ objdump -d -Mintel vrounds*_test

vroundsd_test:     file format elf64-x86-64


Disassembly of section .text:

0000000000201120 <_start>:
  201120:       c4 e3 7d 0b c0 00       vroundsd xmm0,xmm0,xmm0,0x0
  201126:       cc                      int3

vroundss_test:     file format elf64-x86-64


Disassembly of section .text:

0000000000201120 <_start>:
  201120:       c4 e3 7d 0a c0 00       vroundss xmm0,xmm0,xmm0,0x0
  201126:       cc                      int3
$ ./vroundss_test
Trace/breakpoint trap (core dumped)
$ ./vroundsd_test
Trace/breakpoint trap (core dumped)
$ lscpu
Architecture:                x86_64
  CPU op-mode(s):            32-bit, 64-bit
  Address sizes:             46 bits physical, 57 bits virtual
  Byte Order:                Little Endian
CPU(s):                      2
  On-line CPU(s) list:       0,1
Vendor ID:                   GenuineIntel
  Model name:                Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
    CPU family:              6
    Model:                   106
    Thread(s) per core:      2
    Core(s) per socket:      1
    Socket(s):               1
    Stepping:                6
    CPU(s) scaling MHz:      122%
    CPU max MHz:             2800.0000
    CPU min MHz:             800.0000
    BogoMIPS:                5586.87
    Flags:                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse ss
                             e2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology tsc_reliable nonstop
                             _tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 movbe 
                             popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch tpr_sh
                             adow ept vpid ept_ad fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm avx512f avx51
                             2dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsave
                             c xgetbv1 xsaves vnmi avx512vbmi umip avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bita
                             lg avx512_vpopcntdq la57 rdpid fsrm arch_capabilities
Virtualization features:     
  Virtualization:            VT-x
  Hypervisor vendor:         Microsoft
  Virtualization type:       full
Caches (sum of all):         
  L1d:                       48 KiB (1 instance)
  L1i:                       32 KiB (1 instance)
  L2:                        1.3 MiB (1 instance)
  L3:                        48 MiB (1 instance)
NUMA:                        
  NUMA node(s):              1
  NUMA node0 CPU(s):         0,1
Vulnerabilities:             
  Gather data sampling:      Not affected
  Indirect target selection: Vulnerable
  Itlb multihit:             Not affected
  L1tf:                      Not affected
  Mds:                       Not affected
  Meltdown:                  Not affected
  Mmio stale data:           Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
  Reg file data sampling:    Not affected
  Retbleed:                  Vulnerable
  Spec rstack overflow:      Not affected
  Spec store bypass:         Vulnerable
  Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
  Spectre v2:                Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline
  Srbds:                     Not affected
  Tsa:                       Not affected
  Tsx async abort:           Not affected
  Vmscape:                   Not affected
$

AMD EPYC 7763 (from 4-core instance): Same result

$ objdump -d -Mintel vrounds*_test

vroundsd_test:     file format elf64-x86-64


Disassembly of section .text:

0000000000201120 <_start>:
  201120:       c4 e3 7d 0b c0 00       vroundsd xmm0,xmm0,xmm0,0x0
  201126:       cc                      int3

vroundss_test:     file format elf64-x86-64


Disassembly of section .text:

0000000000201120 <_start>:
  201120:       c4 e3 7d 0a c0 00       vroundss xmm0,xmm0,xmm0,0x0
  201126:       cc                      int3
$ ./vroundss_test
Trace/breakpoint trap (core dumped)
$ ./vroundsd_test
Trace/breakpoint trap (core dumped)
$ lscpu
Architecture:                x86_64
  CPU op-mode(s):            32-bit, 64-bit
  Address sizes:             48 bits physical, 48 bits virtual
  Byte Order:                Little Endian
CPU(s):                      4
  On-line CPU(s) list:       0-3
Vendor ID:                   AuthenticAMD
  Model name:                AMD EPYC 7763 64-Core Processor
    CPU family:              25
    Model:                   1
    Thread(s) per core:      2
    Core(s) per socket:      2
    Socket(s):               1
    Stepping:                1
    BogoMIPS:                4890.85
    Flags:                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse ss
                             e2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nons
                             top_tsc cpuid extd_apicid aperfmperf tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 ss
                             e4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy svm cr8_legacy abm ss
                             e4a misalignsse 3dnowprefetch osvw topoext vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid rd
                             seed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves user_shstk clzero xsaveerpt
                             r rdpru arat npt nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthresho
                             ld v_vmsave_vmload umip vaes vpclmulqdq rdpid fsrm
Virtualization features:     
  Virtualization:            AMD-V
  Hypervisor vendor:         Microsoft
  Virtualization type:       full
Caches (sum of all):         
  L1d:                       64 KiB (2 instances)
  L1i:                       64 KiB (2 instances)
  L2:                        1 MiB (2 instances)
  L3:                        32 MiB (1 instance)
NUMA:                        
  NUMA node(s):              1
  NUMA node0 CPU(s):         0-3
Vulnerabilities:             
  Gather data sampling:      Not affected
  Indirect target selection: Not affected
  Itlb multihit:             Not affected
  L1tf:                      Not affected
  Mds:                       Not affected
  Meltdown:                  Not affected
  Mmio stale data:           Not affected
  Reg file data sampling:    Not affected
  Retbleed:                  Not affected
  Spec rstack overflow:      Vulnerable: Safe RET, no microcode
  Spec store bypass:         Vulnerable
  Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
  Spectre v2:                Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected
  Srbds:                     Not affected
  Tsa:                       Vulnerable: Clear CPU buffers attempted, no microcode
  Tsx async abort:           Not affected
  Vmscape:                   Not affected
$ 

@unknown-1-0

unknown-1-0 commented Sep 4, 2026

Copy link
Copy Markdown

It looks like Volume 4 of AMD64 Architecture Programmer's Manual explicitly documents that VEX.L is ignored for both VROUNDSS and VROUNDSD
VROUNDSS_AMD64_APM
VROUNDSD_AMD64_APM

Edit: Intel SDM documents it too:
ROUNDSS_Intel_SDM
ROUNDSD_Intel_SDM

@unknown-1-0

Copy link
Copy Markdown

Patch for upstream QEMU (in case the upstream maintainers find some issues in it): https://lore.kernel.org/qemu-devel/20260905014629.991586-1-apolivodaa433@gmail.com/T/#u

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants