mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-10-19 18:12:14 -09:00
Hexagon: add stub- and packet-coverage regression-pin tests
Two new JUnit tests pin the disassembly text emitted for representative encodings of the recently added decoder stubs (HexagonStubCoverageTest) and packet-context decoding (HexagonPacketCoverageTest), so a future edit that accidentally drops or shadows a constructor will surface as a deliberate review point rather than a silent regression.
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.assembler.sleigh;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
|
||||
import ghidra.app.util.PseudoInstruction;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
|
||||
/**
|
||||
* Packet-context regression-pin coverage test for the Hexagon SLEIGH spec.
|
||||
*
|
||||
* <p>Many Hexagon instruction families (NV-cmp-jumps, NV-stores, HVX
|
||||
* V6_vS32b_new_*, dealloc_return:new, ...) only decode in the context of a
|
||||
* non-zero packet position with prior-slot register tracking. The mainline
|
||||
* canonical-byte oracle test (compare_3way.json) decodes each instruction
|
||||
* standalone and so reports these families as DECODE_FAILED -- but that is a
|
||||
* methodology artifact, not a real spec gap.
|
||||
*
|
||||
* <p>This class constructs the required {@code packetOffset} /
|
||||
* {@code packetBits} context by hand and disassembles the second-or-later
|
||||
* instruction in the packet, asserting the resulting mnemonic and operand
|
||||
* text. The pattern follows
|
||||
* {@code HexagonAssemblyTest#testAssemble_memw_mSP_n0x4_R0new}, which already
|
||||
* exercises packet-context for {@code R0.new} stores.
|
||||
*
|
||||
* <p>Each assertion pins the current spec output for one representative
|
||||
* encoding of one packet-context-dependent family; if the constructor
|
||||
* disappears or its rendering changes the assertion fires.
|
||||
*
|
||||
* <p>The byte encodings below are LLVM canonical primaries (one per
|
||||
* {@code def : HInst<>}) modified only to:
|
||||
* <ul>
|
||||
* <li>set the 3-bit {@code Ns8} / {@code Os8} / {@code Nt8} new-register field
|
||||
* to encoded value {@code 0b010} (= 2). With
|
||||
* {@code nregSlot = packetOffset - xreg - (field >> 1)} and
|
||||
* {@code packetOffset == 1, xreg == 0}, this resolves to
|
||||
* {@code nregSlot = 0} (i.e. the writer was at slot 0);
|
||||
* <li>leave parse bits at the canonical {@code 11} (end-of-packet) since the
|
||||
* reader instruction is the last in the packet.
|
||||
* </ul>
|
||||
*
|
||||
* <p>The 3-bit new-value field lives at:
|
||||
* <ul>
|
||||
* <li>bits 16..18 (Ns8) for NV-cmp-jumps; setting bit 17 of the word lifts
|
||||
* it from {@code 000} to {@code 010};
|
||||
* <li>bits 8..10 (Nt8) for NV-stores; setting bit 9;
|
||||
* <li>bits 0..2 (Os8) for HVX V6_vS32b_new; setting bit 1.
|
||||
* </ul>
|
||||
*/
|
||||
public class HexagonPacketCoverageTest extends AbstractAssemblyTest {
|
||||
|
||||
@Override
|
||||
protected LanguageID getLanguageID() {
|
||||
return new LanguageID("Hexagon:LE:32:default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a context byte string with the given {@code packetOffset} and
|
||||
* {@code packetBits} sub-register values. Mirrors the helper in
|
||||
* {@link HexagonAssemblyTest}.
|
||||
*
|
||||
* @param packetOffset slot index within the packet (0..3)
|
||||
* @param packetBits raw value of the {@code packetBits} sub-register; the
|
||||
* high 2 bits are {@code parse1} (slot-0 parse), the
|
||||
* next 2 are {@code parse2} (slot-1 parse), then 5-bit
|
||||
* {@code nreg0}, {@code nreg1}, {@code nreg2}, etc.
|
||||
*/
|
||||
private String makeCtx(int packetOffset, long packetBits) {
|
||||
RegisterValue ctxVal = new RegisterValue(lang.getContextBaseRegister());
|
||||
ctxVal = ctxVal.assign(lang.getRegister("packetOffset"),
|
||||
BigInteger.valueOf(packetOffset));
|
||||
ctxVal = ctxVal.assign(lang.getRegister("packetBits"),
|
||||
BigInteger.valueOf(packetBits));
|
||||
return AssemblyPatternBlock.fromRegisterValue(ctxVal).fillMask().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a single 4-byte little-endian Hexagon word at packet position
|
||||
* {@code packetOffset} with the given prior-slot {@code packetBits} and
|
||||
* assert the disassembly text.
|
||||
*
|
||||
* @param hexBytes 8 hex chars representing the 4 bytes in
|
||||
* little-endian order as they appear in memory
|
||||
* @param packetOffset slot index within the packet (0..3)
|
||||
* @param packetBits raw {@code packetBits} sub-register value
|
||||
* @param expected expected disassembly text, trimmed
|
||||
*/
|
||||
protected void assertPacketDecode(String hexBytes, int packetOffset, long packetBits,
|
||||
String expected) {
|
||||
if (hexBytes.length() != 8) {
|
||||
throw new IllegalArgumentException(
|
||||
"expected 4-byte hex word (8 chars), got: " + hexBytes);
|
||||
}
|
||||
byte[] bytes = new byte[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bytes[i] = (byte) Integer.parseInt(hexBytes.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
String ctxStr = makeCtx(packetOffset, packetBits);
|
||||
byte[] ctx = AssemblyPatternBlock.fromString(ctxStr).fillMask().getVals();
|
||||
PseudoInstruction pi;
|
||||
try {
|
||||
pi = disassemble(DEFAULT_ADDR, bytes, ctx);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(
|
||||
"disassembly threw for bytes " + hexBytes + " ctx=" + ctxStr +
|
||||
" (expected: " + expected + ")",
|
||||
e);
|
||||
}
|
||||
String actual = pi.toString().trim();
|
||||
assertEquals("bytes " + hexBytes + " offset=" + packetOffset + " packetBits=" +
|
||||
Long.toHexString(packetBits), expected, actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common context for "slot 1 reads slot 0's writer of R0":
|
||||
* <ul>
|
||||
* <li>parse1 = 01 (slot 0 was a normal middle instruction)
|
||||
* <li>nreg0 = 0 (slot 0 wrote to R0; encoded directly into the 5-bit
|
||||
* {@code nreg0} sub-field of {@code packetBits})
|
||||
* </ul>
|
||||
* packetBits = parse1 in high 2 bits = 0b01_00_00000_00000_00000_00000_00000_000
|
||||
* = 0x40000000.
|
||||
*/
|
||||
private static final long PB_S1_NREG0_R0 = 0x40000000L;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// NV cmp-jump families (J4_cmp{eq,gt,gtu}{,i,n1}_{t,f}_jumpnv_{nt,t})
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Encoding layout:
|
||||
// 31..28 : iclass = 0010
|
||||
// 27..22 : opcode bits selecting cmp.{eq,gt,gtu} and t/f
|
||||
// 18..16 : Ns8 (3-bit new-value source)
|
||||
// 15..14 : parse bits
|
||||
// 13 : taken hint (1=jump:t, 0=jump:nt)
|
||||
// 12..8 : Rt32 (or U5 immediate)
|
||||
// 7..1 : i7 immediate
|
||||
//
|
||||
// Canonical sets Ns8 = 000; we set bit 17 -> Ns8 = 010. In LE memory
|
||||
// order byte2 (bits 16..23) gets bit 1 set: byte2 |= 0x02.
|
||||
//
|
||||
// Mainline display order:
|
||||
// "jump.if:<t|nt> [!]cmp.<eq|gt|gtu>(R0.new,<rhs>),<target>"
|
||||
// Jump target = inst_start - 4*packetOffset + sext(imm)*4. With
|
||||
// DEFAULT_ADDR = 0x40000000, packetOffset=1, imm=0: 0x40000000 - 4 = 0x3ffffffc.
|
||||
|
||||
/** Pin the J4_cmpeq*_*_jumpnv_* families. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVCmpJump_eq() {
|
||||
// J4_cmpeq_t_jumpnv_t (canonical 00e00020)
|
||||
assertPacketDecode("00e00220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.eq(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpeq_t_jumpnv_nt (canonical 00c00020)
|
||||
assertPacketDecode("00c00220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:nt cmp.eq(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpeq_f_jumpnv_t (canonical 00e04020)
|
||||
assertPacketDecode("00e04220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.eq(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpeqi_t_jumpnv_t (canonical 00e00024)
|
||||
assertPacketDecode("00e00224", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.eq(R0.new,#0x0),0x3ffffffc");
|
||||
// J4_cmpeqi_f_jumpnv_t (canonical 00e04024)
|
||||
assertPacketDecode("00e04224", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.eq(R0.new,#0x0),0x3ffffffc");
|
||||
// J4_cmpeqn1_t_jumpnv_t (canonical 00e00026) -- #-1 literal renders as "#-1"
|
||||
assertPacketDecode("00e00226", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.eq(R0.new,#-1),0x3ffffffc");
|
||||
// J4_cmpeqn1_f_jumpnv_t (canonical 00e04026)
|
||||
assertPacketDecode("00e04226", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.eq(R0.new,#-1),0x3ffffffc");
|
||||
}
|
||||
|
||||
/** Pin the J4_cmpgt*_*_jumpnv_* families. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVCmpJump_gt() {
|
||||
// J4_cmpgt_t_jumpnv_t (canonical 00e08020)
|
||||
assertPacketDecode("00e08220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.gt(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgt_t_jumpnv_nt
|
||||
assertPacketDecode("00c08220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:nt cmp.gt(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgt_f_jumpnv_t (canonical 00e0c020)
|
||||
assertPacketDecode("00e0c220", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.gt(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgti_t_jumpnv_t (canonical 00e08024)
|
||||
assertPacketDecode("00e08224", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.gt(R0.new,#0x0),0x3ffffffc");
|
||||
// J4_cmpgtn1_t_jumpnv_t (canonical 00e08026) -- #-1 literal
|
||||
assertPacketDecode("00e08226", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.gt(R0.new,#-1),0x3ffffffc");
|
||||
}
|
||||
|
||||
/** Pin the J4_cmpgtu*_*_jumpnv_* families. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVCmpJump_gtu() {
|
||||
// J4_cmpgtu_t_jumpnv_t (canonical 00e00021)
|
||||
assertPacketDecode("00e00221", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.gtu(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgtu_t_jumpnv_nt (canonical 00c00021)
|
||||
assertPacketDecode("00c00221", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:nt cmp.gtu(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgtu_f_jumpnv_t (canonical 00e04021)
|
||||
assertPacketDecode("00e04221", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.gtu(R0.new,R0),0x3ffffffc");
|
||||
// J4_cmpgtui_t_jumpnv_t (canonical 00e00025)
|
||||
assertPacketDecode("00e00225", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t cmp.gtu(R0.new,#0x0),0x3ffffffc");
|
||||
// J4_cmpgtui_f_jumpnv_t (canonical 00e04025)
|
||||
assertPacketDecode("00e04225", 1, PB_S1_NREG0_R0,
|
||||
"jump.if:t !cmp.gtu(R0.new,#0x0),0x3ffffffc");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// NV-store families (S2_storer{b,h,i}new_*, S4_storer{b,h,i}new_*)
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Encoding layout (S2_storerXnew_io, _pi, _pci, _rr):
|
||||
// 31..28 : iclass = 1010 (0xa) for S2_*; 0011_1011 etc. for S4_*
|
||||
// 27..22 : subclass selecting size & addressing mode
|
||||
// 21..16 : Rs32 (base reg)
|
||||
// 15..14 : parse bits
|
||||
// 13..11 : 000
|
||||
// 10..8 : Nt8 (3-bit new-value source)
|
||||
// 7..3 : Ii immediate
|
||||
//
|
||||
// Canonical Nt8 = 000; we set bit 9 of word -> Nt8 = 010. In LE memory
|
||||
// order, byte1 |= 0x02. With imm=0 the rendering omits the offset:
|
||||
// "memb (R0),R0.new" not "memb (R0+#0x0),R0.new".
|
||||
|
||||
/** Pin S2_storerbnew_io / S2_storerbnew_pi / S4_storerbnew_rr. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVStore_byte() {
|
||||
// S2_storerbnew_io (canonical 00c0a0a1) imm=0 -> "(R0)" only
|
||||
assertPacketDecode("00c2a0a1", 1, PB_S1_NREG0_R0,
|
||||
"memb (R0),R0.new");
|
||||
// S2_storerbnew_pi (canonical 00c0a0ab)
|
||||
assertPacketDecode("00c2a0ab", 1, PB_S1_NREG0_R0,
|
||||
"memb (R0++#0x0),R0.new");
|
||||
// S4_storerbnew_rr (canonical 00c0a03b)
|
||||
// Note: this constructor uses Nreg0002 (Nt8 at bits 0..2), NOT Nreg0810,
|
||||
// so we set bit 1 (byte0 |= 0x02) rather than bit 9.
|
||||
// With shift = 0 the rendering omits the "<<#0x0" suffix.
|
||||
assertPacketDecode("02c0a03b", 1, PB_S1_NREG0_R0,
|
||||
"memb (R0+R0),R0.new");
|
||||
}
|
||||
|
||||
/** Pin S2_storerhnew_io / S2_storerhnew_pi / S4_storerhnew_rr. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVStore_half() {
|
||||
// S2_storerhnew_io (canonical 00c8a0a1)
|
||||
assertPacketDecode("00caa0a1", 1, PB_S1_NREG0_R0,
|
||||
"memh (R0),R0.new");
|
||||
// S2_storerhnew_pi (canonical 00c8a0ab)
|
||||
assertPacketDecode("00caa0ab", 1, PB_S1_NREG0_R0,
|
||||
"memh (R0++#0x0),R0.new");
|
||||
// S4_storerhnew_rr (canonical 08c0a03b) -- Nreg0002 -> set bit 1
|
||||
assertPacketDecode("0ac0a03b", 1, PB_S1_NREG0_R0,
|
||||
"memh (R0+R0),R0.new");
|
||||
}
|
||||
|
||||
/** Pin S2_storerinew_io / S2_storerinew_pi / S4_storerinew_rr. */
|
||||
@Test
|
||||
public void testPacketCoverage_NVStore_word() {
|
||||
// S2_storerinew_io (canonical 00d0a0a1)
|
||||
assertPacketDecode("00d2a0a1", 1, PB_S1_NREG0_R0,
|
||||
"memw (R0),R0.new");
|
||||
// S2_storerinew_pi (canonical 00d0a0ab)
|
||||
assertPacketDecode("00d2a0ab", 1, PB_S1_NREG0_R0,
|
||||
"memw (R0++#0x0),R0.new");
|
||||
// S4_storerinew_rr (canonical 10c0a03b) -- Nreg0002 -> set bit 1
|
||||
assertPacketDecode("12c0a03b", 1, PB_S1_NREG0_R0,
|
||||
"memw (R0+R0),R0.new");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// HVX V6_vS32b_new_* (vector NV stores)
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Encoding for V6_vS32b_new_ai (canonical 20c02028 -> word 0x2820c020):
|
||||
// 31..28 : iclass = 0010
|
||||
// 27..21 : 1010_000 (vS32b family subclass)
|
||||
// ...
|
||||
// 2..0 : Os8 (3-bit new vector reg, via VNreg0002 sub-constructor)
|
||||
//
|
||||
// Canonical Os8 = 0; we set bit 1 of word -> Os8 = 010.
|
||||
// In LE memory order, byte0 |= 0x02.
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Predicated NV-store families (S4_pstorer{b,h,i}new{t,f}{,new}_*)
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Encoding for S4_pstorerbnewt_io (predicated NV store):
|
||||
// 31..28 : iclass = 0100 (0x4) [for the io form]
|
||||
// ...
|
||||
// 10..8 : Nt8 (Nreg0810)
|
||||
// We set Nt8 = 010 -> bit 9 of word, byte1 |= 0x02.
|
||||
//
|
||||
// For S4_pstorerXnewtnew_rr / S4_pstorerXnewfnew_rr (P.new + register
|
||||
// indexed) the canonical bytes from JSON include the
|
||||
// {@code <<#0x0} shift = 0; we set bit 1 (Nreg0002) on those.
|
||||
|
||||
/** Pin S4 predicated NV-store family. */
|
||||
@Test
|
||||
public void testPacketCoverage_PStoreNew_pred() {
|
||||
// S4_pstorerbnewt_rr (canonical 00c0a034) -- if (P0) memb(...) = R0.new
|
||||
// Uses Nreg0002, set bit 1
|
||||
assertPacketDecode("02c0a034", 1, PB_S1_NREG0_R0,
|
||||
"memb.if(P0) (R0+R0),R0.new");
|
||||
// S4_pstorerbnewf_rr (canonical 00c0a035) -- if (!P0)
|
||||
assertPacketDecode("02c0a035", 1, PB_S1_NREG0_R0,
|
||||
"memb.if(!P0) (R0+R0),R0.new");
|
||||
// S4_pstorerinewtnew_rr (canonical 10c0a036) -- if (P0.new)
|
||||
assertPacketDecode("12c0a036", 1, PB_S1_NREG0_R0,
|
||||
"memw.if(P0.new) (R0+R0),R0.new");
|
||||
// S4_pstorerhnewfnew_rr (canonical 08c0a037) -- if (!P0.new)
|
||||
assertPacketDecode("0ac0a037", 1, PB_S1_NREG0_R0,
|
||||
"memh.if(!P0.new) (R0+R0),R0.new");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// L4_return_*new_p* (dealloc_return predicated by P.new)
|
||||
// ---------------------------------------------------------------------
|
||||
//
|
||||
// Encoding for "if (Ps4.new) dealloc_return:t" (LLVM L4_return_tnew_pt):
|
||||
// 31..28 : 1001 (iclass=9)
|
||||
// 27..21 : 0110000 (op2127=0x30)
|
||||
// 20..16 : 11110 (op1620=0x1e -- fixed pattern, NOT a register
|
||||
// field; this distinguishes the form from
|
||||
// "Rdd32 = dealloc_return(Rs32)" which puts an
|
||||
// rs5 register here)
|
||||
// 15..14 : PP (parse bits)
|
||||
// 13 : 0/1 = jump:t/jump:nt (DRTaken12)
|
||||
// 12..11 : 11 (selects "if (..new)" path)
|
||||
// 10..9 : 10/01 selecting P.new and/or "!"
|
||||
// 7..1 : (zero in our test)
|
||||
// 4..0 : 11110 (op0007=0x1e fixed)
|
||||
//
|
||||
// Note: LLVM canonical encoding sets the 5 bits at op1620 and the low 5
|
||||
// bits of op0007 to 0 (these encode placeholder operands like Rs32 and
|
||||
// Rdd32 in LLVM's view). Mainline matches the predicated-with-rs5 form
|
||||
// (which has rs5 at bits 16..20 and op0007=0x1e fixed), so canonical
|
||||
// bytes 00d80096 from compare_3way.json don't decode in mainline:
|
||||
// the low-5 bits 0x00 don't match op0007=0x1e. We supply byte0=0x1e.
|
||||
//
|
||||
// rs5 = 0 (R0) -> byte2 bits 0..4 = 0; byte2 bits 5..7 = 0 (op2127 low).
|
||||
// byte3 bits 0..3 = 0110 (op2127 high), bits 4..7 = 1001 (iclass=9) ->
|
||||
// byte3 = 0x96. So bytes for "if (P0.new) dealloc_return:t R0":
|
||||
// byte0 = 0x1e, byte1 = 0xd8, byte2 = 0x00, byte3 = 0x96 -> "1ed80096".
|
||||
|
||||
// NOTE: a non-canonical dealloc_return renders with its explicit destination
|
||||
// pair (":raw Rdd,Rs"); the bare "dealloc_return" spelling is reserved for the
|
||||
// canonical Rs=R30 / Rdd=R31:30 encoding. These use rs5=R0, so they decode as
|
||||
// ":raw R31R30,R0".
|
||||
/** Pin L4_return_*new_p* (dealloc_return predicated by P.new) family. */
|
||||
@Test
|
||||
public void testPacketCoverage_DeallocReturnNew() {
|
||||
// L4_return_tnew_pt -- "if (P0.new) dealloc_return:t" rs5=R0
|
||||
// byte1: parse=11, bit13=0, bit12=1, bit11=1, bit10=0, bits9..8=00 = 0xd8
|
||||
assertPacketDecode("1ed80096", 1, PB_S1_NREG0_R0,
|
||||
"dealloc_return.if(P0.new):t:raw R31R30,R0");
|
||||
// L4_return_tnew_pnt -- "if (P0.new) dealloc_return:nt"
|
||||
// bit12=0 -> byte1 = 11_0_0_1_0_00 = 0xc8
|
||||
assertPacketDecode("1ec80096", 1, PB_S1_NREG0_R0,
|
||||
"dealloc_return.if(P0.new):nt:raw R31R30,R0");
|
||||
// L4_return_fnew_pt -- "if (!P0.new) dealloc_return:t"
|
||||
// bit13=1 -> byte1 = 11_1_1_1_0_00 = 0xf8
|
||||
assertPacketDecode("1ef80096", 1, PB_S1_NREG0_R0,
|
||||
"dealloc_return.if(!P0.new):t:raw R31R30,R0");
|
||||
// L4_return_fnew_pnt -- "if (!P0.new) dealloc_return:nt"
|
||||
// bit13=1, bit12=0 -> byte1 = 11_1_0_1_0_00 = 0xe8
|
||||
assertPacketDecode("1ee80096", 1, PB_S1_NREG0_R0,
|
||||
"dealloc_return.if(!P0.new):nt:raw R31R30,R0");
|
||||
}
|
||||
|
||||
/** Pin HVX V6_vS32b_new_* family. */
|
||||
@Test
|
||||
public void testPacketCoverage_HVX_VS32bNew() {
|
||||
// V6_vS32b_new_ai (canonical 20c02028) -> set bit 1: byte0 = 0x22
|
||||
assertPacketDecode("22c02028", 1, PB_S1_NREG0_R0,
|
||||
"vmem (R0),V0.new");
|
||||
// V6_vS32b_new_pi (canonical 20c02029)
|
||||
assertPacketDecode("22c02029", 1, PB_S1_NREG0_R0,
|
||||
"vmem (R0++#0x0),V0.new");
|
||||
// V6_vS32b_new_pred_ai (canonical 40c0a028) -- mainline renders as
|
||||
// "vmem.if(P0)" rather than "if (P0) vmem".
|
||||
assertPacketDecode("42c0a028", 1, PB_S1_NREG0_R0,
|
||||
"vmem.if(P0) (R0),V0.new");
|
||||
// V6_vS32b_new_npred_ai (canonical 68c0a028)
|
||||
assertPacketDecode("6ac0a028", 1, PB_S1_NREG0_R0,
|
||||
"vmem.if(!P0) (R0),V0.new");
|
||||
// V6_vS32b_new_ppu (canonical 20c0202b) -- Mu2 post-increment
|
||||
assertPacketDecode("22c0202b", 1, PB_S1_NREG0_R0,
|
||||
"vmem (R0++M0),V0.new");
|
||||
// V6_vS32b_new_pred_pi (canonical 40c0a029) -- predicated post-inc imm
|
||||
assertPacketDecode("42c0a029", 1, PB_S1_NREG0_R0,
|
||||
"vmem.if(P0) (R0++#0x0),V0.new");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.assembler.sleigh;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.util.PseudoInstruction;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
|
||||
/**
|
||||
* Regression-pin coverage test for recently added decoder stubs.
|
||||
*
|
||||
* <p>Each assertion records, as a baseline, the disassembly text produced by
|
||||
* the current Hexagon SLEIGH spec for a representative encoding of one stub
|
||||
* family. The intent is purely to detect silent behavioral regressions
|
||||
* (e.g. a future edit that accidentally removes or shadows a decoder
|
||||
* constructor): if the decoded mnemonic changes for any pinned encoding,
|
||||
* the corresponding assertion will fail and force a deliberate review.
|
||||
*
|
||||
* <p>Encodings are taken from real LLVM-MC sample bytes for V69/V73 HVX and
|
||||
* system instructions; the expected text is what the spec currently emits
|
||||
* (which for some families is a deliberately simplified or stubbed form).
|
||||
*
|
||||
* <p>Unlike {@link HexagonAssemblyTest} this class does <em>not</em> exercise
|
||||
* the assembler -- many of the stubbed mnemonics use punctuation
|
||||
* (e.g. {@code |=}, {@code &=}, {@code ^=}, {@code +=}, {@code :rnd:sat})
|
||||
* that does not necessarily round-trip through the assembler grammar.
|
||||
* We only assert byte-to-text disassembly via {@link #disassemble}.
|
||||
*/
|
||||
public class HexagonStubCoverageTest extends AbstractAssemblyTest {
|
||||
|
||||
@Override
|
||||
protected LanguageID getLanguageID() {
|
||||
return new LanguageID("Hexagon:LE:32:default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a single 4-byte little-endian Hexagon word and assert the
|
||||
* disassembly text (trimmed) matches {@code expected}.
|
||||
*
|
||||
* @param hexBytes 8 hex chars representing the 4 bytes in little-endian
|
||||
* order as they appear in memory (the same form that the
|
||||
* LLVM-MC corpus prints)
|
||||
* @param expected the expected disassembly, without trailing whitespace
|
||||
*/
|
||||
protected void assertDecode(String hexBytes, String expected) {
|
||||
if (hexBytes.length() != 8) {
|
||||
throw new IllegalArgumentException(
|
||||
"expected 4-byte hex word (8 chars), got: " + hexBytes);
|
||||
}
|
||||
byte[] bytes = new byte[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bytes[i] = (byte) Integer.parseInt(hexBytes.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
// Default context for a single-instruction packet; the parse-bits
|
||||
// in the encoding itself mark end-of-packet.
|
||||
byte[] ctx = context.getDefaultAt(lang.getDefaultSpace().getAddress(DEFAULT_ADDR))
|
||||
.fillMask()
|
||||
.getVals();
|
||||
PseudoInstruction pi;
|
||||
try {
|
||||
pi = disassemble(DEFAULT_ADDR, bytes, ctx);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new AssertionError(
|
||||
"disassembly threw for bytes " + hexBytes + " (expected: " + expected + ")", e);
|
||||
}
|
||||
String actual = pi.toString().trim();
|
||||
assertEquals("bytes " + hexBytes, expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_vmpy_integer() {
|
||||
// V6_vmpyhus
|
||||
assertDecode("40c0201c", "vmpy V1V0.w,V0.h,V0.uh");
|
||||
// V6_vmpyhus_acc
|
||||
assertDecode("20e0201c", "vmpy+= V1V0.w,V0.h,V0.uh");
|
||||
// V6_vmpyhv
|
||||
assertDecode("e0c0001c", "vmpy V1V0.w,V0.h,V0.h");
|
||||
// V6_vmpyhv_acc
|
||||
assertDecode("e0e0001c", "vmpy+= V1V0.w,V0.h,V0.h");
|
||||
// V6_vmpyhvsrs
|
||||
assertDecode("20c0201c", "vmpy:<<1:rnd:sat V0.h,V0.h,V0.h");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_vrmpy_rtt() {
|
||||
// V6_vrmpybub_rtt
|
||||
assertDecode("a0c0c019", "vrmpy V1V0.w,V0.b,R1R0.ub");
|
||||
// V6_vrmpybub_rtt_acc
|
||||
assertDecode("00e0a019", "vrmpy+= V1V0.w,V0.b,R1R0.ub");
|
||||
// V6_vrmpyub_rtt
|
||||
assertDecode("80c0c019", "vrmpy V1V0.uw,V0.ub,R1R0.ub");
|
||||
// V6_vrmpyub_rtt_acc
|
||||
assertDecode("e0e0a019", "vrmpy+= V1V0.uw,V0.ub,R1R0.ub");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_hf_sf_arith() {
|
||||
// V6_vdmpy_sf_hf_acc
|
||||
assertDecode("60e0401c", "vdmpy+= V0.sf,V0.hf,V0.hf");
|
||||
// V6_vmpy_hf_hf_acc
|
||||
assertDecode("40e0401c", "vmpy+= V0.hf,V0.hf,V0.hf");
|
||||
// V6_vmpy_sf_hf_acc
|
||||
assertDecode("20e0401c", "vmpy+= V1V0.sf,V0.hf,V0.hf");
|
||||
// V6_vfmax_hf
|
||||
assertDecode("40e0601c", "vfmax V0.hf,V0.hf,V0.hf");
|
||||
// V6_vfmax_sf
|
||||
assertDecode("60e0601c", "vfmax V0.sf,V0.sf,V0.sf");
|
||||
// V6_vfmin_hf
|
||||
assertDecode("00e0601c", "vfmin V0.hf,V0.hf,V0.hf");
|
||||
// V6_vfmin_sf
|
||||
assertDecode("20e0601c", "vfmin V0.sf,V0.sf,V0.sf");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_bf_arith() {
|
||||
// V6_vadd_sf_bf
|
||||
assertDecode("c0e0401d", "vadd V1V0.sf,V0.bf,V0.bf");
|
||||
// V6_vsub_sf_bf
|
||||
assertDecode("a0e0401d", "vsub V1V0.sf,V0.bf,V0.bf");
|
||||
// V6_vmpy_sf_bf
|
||||
assertDecode("80e0401d", "vmpy V1V0.sf,V0.bf,V0.bf");
|
||||
// V6_vmpy_sf_bf_acc
|
||||
assertDecode("00e0001d", "vmpy+= V1V0.sf,V0.bf,V0.bf");
|
||||
// V6_vmax_bf
|
||||
assertDecode("e0e0401d", "vmax V0.bf,V0.bf,V0.bf");
|
||||
// V6_vmin_bf
|
||||
assertDecode("00e0401d", "vmin V0.bf,V0.bf,V0.bf");
|
||||
// V6_vcvt_bf_sf
|
||||
assertDecode("60e0401d", "vcvt V0.bf,V0.sf,V0.sf");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_bf_compare() {
|
||||
// V6_vgtbf
|
||||
assertDecode("78e0801c", "vcmp.gt Q0,V0.bf,V0.bf");
|
||||
// V6_vgtbf_and
|
||||
assertDecode("d0e0801c", "vcmp.gt&= Q0,V0.bf,V0.bf");
|
||||
// V6_vgtbf_or
|
||||
assertDecode("38e0801c", "vcmp.gt|= Q0,V0.bf,V0.bf");
|
||||
// V6_vgtbf_xor
|
||||
assertDecode("f0e0801c", "vcmp.gt^= Q0,V0.bf,V0.bf");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_hf_sf_eq_compare() {
|
||||
// V6_veqhf_and
|
||||
assertDecode("1ce0801c", "vcmp.eq&= Q0,V0.hf,V0.hf");
|
||||
// V6_veqhf_or
|
||||
assertDecode("5ce0801c", "vcmp.eq|= Q0,V0.hf,V0.hf");
|
||||
// V6_veqhf_xor
|
||||
assertDecode("9ce0801c", "vcmp.eq^= Q0,V0.hf,V0.hf");
|
||||
// V6_veqsf_and
|
||||
assertDecode("0ce0801c", "vcmp.eq&= Q0,V0.sf,V0.sf");
|
||||
// V6_veqsf_or
|
||||
assertDecode("4ce0801c", "vcmp.eq|= Q0,V0.sf,V0.sf");
|
||||
// V6_veqsf_xor
|
||||
assertDecode("8ce0801c", "vcmp.eq^= Q0,V0.sf,V0.sf");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_f8_and_cvt2() {
|
||||
// V6_vcvt2_b_hf
|
||||
assertDecode("c0e0c01a", "vcvt2 V0.b,V0.hf,V0.hf");
|
||||
// V6_vcvt2_ub_hf
|
||||
assertDecode("e0e0c01a", "vcvt2 V0.ub,V0.hf,V0.hf");
|
||||
// V6_vfmax_f8
|
||||
assertDecode("a0e0601c", "vfmax V0.f8,V0.f8,V0.f8");
|
||||
// V6_vfmin_f8
|
||||
assertDecode("80e0601c", "vfmin V0.f8,V0.f8,V0.f8");
|
||||
// V6_vabs_f8 -- previously broken (typo bug), now decoded
|
||||
assertDecode("c0e0661c", "vabs V0.f8,V0.f8");
|
||||
// V6_vfneg_f8 -- previously broken (typo bug), now decoded
|
||||
assertDecode("e0e0661c", "vfneg V0.f8,V0.f8");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_vhist() {
|
||||
// V6_vhist -- previously broken (typo bug), now decoded
|
||||
assertDecode("80e0001e", "vhist");
|
||||
// V6_vhistq -- previously broken (typo bug), now decoded
|
||||
assertDecode("80e0021e", "vhist Q0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_HVX_qfext_and_align() {
|
||||
// V6_get_qfext
|
||||
assertDecode("e0c0c019", "vgetqfext V0,V0.x,R0");
|
||||
// V6_get_qfext_oracc
|
||||
assertDecode("c0c0c019", "vgetqfext|= V0,V0.x,R0");
|
||||
// V6_set_qfext
|
||||
assertDecode("60c0c019", "vsetqfext V0.x,V0,R0");
|
||||
// V6_valign4
|
||||
assertDecode("a0c00018", "valign4 V0,V0,V0,R0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_scalar_and_pair() {
|
||||
// A6_vminub_RdP
|
||||
assertDecode("00c0e0ea", "vminub R1R0,P0,R1R0,R1R0");
|
||||
// L2_loadw_aq
|
||||
assertDecode("00c80092", "memw_aq R0,R0");
|
||||
// L6_memcpy
|
||||
assertDecode("40c00092", "memcpy R0,R0,M0");
|
||||
// S6_vtrunehb_ppp -- previously broken (typo bug), now decoded
|
||||
assertDecode("60c080c1", "vtrunehb R1R0,R1R0,R1R0");
|
||||
// S6_vtrunohb_ppp -- previously broken (typo bug), now decoded
|
||||
assertDecode("a0c080c1", "vtrunohb R1R0,R1R0,R1R0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStubCoverage_system_and_dma() {
|
||||
// Y2_icdataw
|
||||
assertDecode("00e0c055", "icdataw R0,R0");
|
||||
// Y2_tlbpp
|
||||
assertDecode("00c0606c", "tlbp R0,R1R0");
|
||||
// Y6_dmlink
|
||||
assertDecode("40c000a6", "dmlink R0,R0");
|
||||
// Y6_dmpause
|
||||
assertDecode("60c000a8", "dmpause R0");
|
||||
// Y6_dmpoll
|
||||
assertDecode("40c000a8", "dmpoll R0");
|
||||
// Y6_dmresume
|
||||
assertDecode("80c000a6", "dmresume R0");
|
||||
// Y6_dmstart
|
||||
assertDecode("20c000a6", "dmstart R0");
|
||||
// Y6_dmwait
|
||||
assertDecode("20c000a8", "dmwait R0");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user