From: Guinevere Larsen Date: Tue, 10 Jun 2025 14:28:25 +0000 (-0300) Subject: gdb/record: add support to vinsert and vextract instructions X-Git-Tag: binutils-2_45~80 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=32be35c9e3159e949edc3d4a957d347f1c7b8dad;p=thirdparty%2Fbinutils-gdb.git gdb/record: add support to vinsert and vextract instructions This patch adds support for the following instructions: * VEXTRACT[F128|I128|PS] * VINSERT[F128|I128|PS] * VPEXTR[B|W|D|Q] And associated test. For some reason, it seems that the extract instructions deal with the output register as though it was the first source register, so they use ModRM.r/m and VEX.B, instead of the usual ModRM.reg and VEX.R. This meant that the opcode collision with vbroadcastsd wasn't trivial. It can be easily solved by checking the VEX.map_select field, so soslving it was very easy. The VPEXTR instructions had several complicated collisions, and notably, vpextrw to a register works completely different to any other instruction in the family, so the code is messy, but it should be correct. --- diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c index 31a113a729a..45ae8b65e3d 100644 --- a/gdb/i386-tdep.c +++ b/gdb/i386-tdep.c @@ -4859,12 +4859,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r, i386_record_lea_modrm (ir); } break; - case 0x14: /* VUNPCKL[PS|PD]. */ - case 0x15: /* VUNPCKH [PS|PD]. */ - i386_record_modrm (ir); - record_full_arch_list_add_reg (ir->regcache, - tdep->ymm0_regnum + ir->reg + vex_r * 8); - break; case 0x6e: /* VMOVD XMM, reg/mem */ /* This is moving from a regular register or memory region into an XMM register. */ @@ -4994,14 +4988,34 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r, } break; - case 0x19: /* VBROADCASTSD. */ + case 0x17: /* VEXTRACTPS. */ i386_record_modrm (ir); record_full_arch_list_add_reg (ir->regcache, - tdep->ymm0_regnum + ir->reg - + 8 * vex_r); + ir->regmap[X86_RECORD_REAX_REGNUM + + ir->rm]); + break; + + case 0x19: /* VBROADCASTSD and VEXTRACTF128. */ + case 0x39: /* VEXTRACTI128. */ + i386_record_modrm (ir); + /* vextract instructions use ModRM.R/M and VEX.B to address the + output register, while vbroadcast use ModRM.Reg and VEX.R. + They are differentiated through map_select. */ + if (ir->map_select == 2) + record_full_arch_list_add_reg (ir->regcache, + tdep->ymm0_regnum + ir->reg + + 8 * vex_r); + else + record_full_arch_list_add_reg (ir->regcache, + tdep->ymm0_regnum + ir->rm + + 8 * ir->rex_b); break; - case 0x18: /* VBROADCASTSS. */ + case 0x18: /* VBROADCASTSS and VINSERTI128. */ + case 0x20: /* VPINSRB. */ + case 0x21: /* VINSERTPS. */ + case 0x22: /* VINSR[D|Q]. */ + case 0x38: /* VINSERTF128. */ case 0x60: /* VPUNPCKLBW */ case 0x61: /* VPUNPCKLWD */ case 0x62: /* VPUNPCKLDQ */ @@ -5010,6 +5024,7 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r, case 0x69: /* VPUNPCKHWD */ case 0x6a: /* VPUNPCKHDQ */ case 0x6d: /* VPUNPCKHQDQ */ + case 0xc4: /* VPINSRW. */ { i386_record_modrm (ir); int reg_offset = ir->reg + vex_r * 8; @@ -5018,6 +5033,61 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r, } break; + case 0x14: /* VPEXTRB and VUNPCKL[PS|PD]. */ + case 0x15: /* VPEXTRW (to memory) and VUNPCKH [PS|PD]. */ + case 0x16: /* VPEXTR[D|Q] and VPERMPS. */ + { + i386_record_modrm (ir); + /* All vpextr instructions in this case use map_select == 3, + while vpermps and vunpck have map_select equal to 2 and 1, + respectively. The opcode 0xc5 is for vpextr, but uses + map_select == 1, but due to other inconsistencies with + the other vpextr instructions, it is in a separate case to + avoid making this even more of a mess. */ + if (ir->map_select == 3) + { + if (ir->mod == 3) + { + /* ModRM.Mod being equal to 3 means this ModRM encodes + a register. */ + record_full_arch_list_add_reg (ir->regcache, + ir->regmap[X86_RECORD_REAX_REGNUM + + ir->rm]); + } + else + { + /* Even though the test only generated ModRM.Mod == 0, + in theory all values != 3 are viable to encode a memory + address, so all of them are passed along. */ + /* Size is mostly based on the opcode, except for + double/quadword difference. */ + ir->ot = opcode - 0x14; + if (opcode == 0x16 && vex_w == 1) + ir->ot ++; + /* I'm not sure if this is the original use, but in here + rip_offset is used to indicate that the RIP pointer will + be 1 byte away from where the instruction expects it to + be, because the immediate will not have been read by the + time the address changed is calculated. */ + ir->rip_offset = 1; + i386_record_lea_modrm (ir); + } + } + else + { + record_full_arch_list_add_reg (ir->regcache, + tdep->ymm0_regnum + ir->reg + + vex_r * 8); + } + break; + } + case 0xc5: /* VPEXTRW to register. */ + i386_record_modrm (ir); + record_full_arch_list_add_reg (ir->regcache, + ir->regmap[X86_RECORD_REAX_REGNUM + + ir->reg]); + break; + case 0x74: /* VPCMPEQB */ case 0x75: /* VPCMPEQB */ case 0x76: /* VPCMPEQB */ @@ -5045,7 +5115,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r, case 0x06: /* VMPERM2F128. */ case 0x0c: /* VPERMILPS with register. */ case 0x0d: /* VPERMILPD with register. */ - case 0x16: /* VPERMPS. */ case 0x1a: /* VBROADCASTF128. */ case 0x36: /* VPERMD. */ case 0x40: /* VPMULLD */ diff --git a/gdb/testsuite/gdb.reverse/i386-avx-reverse.c b/gdb/testsuite/gdb.reverse/i386-avx-reverse.c index 8b3f7077e45..ef7d0d86f50 100644 --- a/gdb/testsuite/gdb.reverse/i386-avx-reverse.c +++ b/gdb/testsuite/gdb.reverse/i386-avx-reverse.c @@ -589,6 +589,45 @@ permute_test () return 0; /* end permute_test */ } +int +extract_insert_test () +{ + /* start extract_insert_test. */ + /* Using GDB, load these values onto registers for testing. + ymm0.v2_int128 = {0, 0} + ymm1.v16_int16 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16} + xmm2.uint128 = 0xcafe + ymm15.v2_int128 = {0x0, 0x0} + eax = 0 + this way it's easy to confirm we're undoing things correctly. */ + + asm volatile ("vinserti128 $1, %xmm2, %ymm1, %ymm0"); + asm volatile ("vinsertf128 $0, %xmm2, %ymm1, %ymm15"); + asm volatile ("vextracti128 $1, %ymm1, %xmm0"); + asm volatile ("vextractf128 $0, %ymm1, %xmm15"); + asm volatile ("vinsertps $16, %xmm2, %xmm1, %xmm0"); + asm volatile ("vextractps $0, %xmm2, %rax"); + + asm volatile ("vpextrb $5, %xmm1, %rax"); + asm volatile ("vpextrb $4, %%xmm1, %0" : "=m" (global_buf1)); + asm volatile ("vpextrd $3, %xmm1, %eax"); + asm volatile ("vpextrd $2, %%xmm1, %0" : "=m" (global_buf1)); + asm volatile ("vpextrq $1, %xmm1, %rax"); + asm volatile ("vpextrq $0, %%xmm1, %0" : "=m" (global_buf1)); + + asm volatile ("vpinsrb $3, %rax, %xmm2, %xmm0"); + asm volatile ("vpinsrw $2, %eax, %xmm2, %xmm15"); + asm volatile ("vpinsrd $1, %eax, %xmm2, %xmm0"); + asm volatile ("vpinsrq $0, %rax, %xmm2, %xmm15"); + + /* vpextrw has completely different mechanics to other vpextr + instructions, so separate them for ease of testing later. */ + asm volatile ("vpextrw $1, %xmm1, %eax"); + asm volatile ("vpextrw $1, %%xmm1, %0" : "=m" (global_buf1)); + + return 0; /* end extract_insert_test */ +} + /* This include is used to allocate the dynamic buffer and have the pointers aligned to a 32-bit boundary, so we can test instructions that require aligned memory. */ @@ -624,5 +663,6 @@ main () shift_test (); shuffle_test (); permute_test (); + extract_insert_test (); return 0; /* end of main */ } diff --git a/gdb/testsuite/gdb.reverse/i386-avx-reverse.exp b/gdb/testsuite/gdb.reverse/i386-avx-reverse.exp index ae13a6e86e6..36e611fe0dd 100644 --- a/gdb/testsuite/gdb.reverse/i386-avx-reverse.exp +++ b/gdb/testsuite/gdb.reverse/i386-avx-reverse.exp @@ -959,3 +959,58 @@ if {[record_full_function "permute"] == true} { } gdb_test "finish" "Run till exit from.*permute_test.*" \ "leaving permute" + +# Preparation and testing extract_insert instructions. +gdb_test_no_output \ + "set \$ymm0.v2_int128 = {0, 0}" "set ymm0 for extract_insert" +gdb_test_no_output \ + "set \$ymm1.v16_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}" \ + "set ymm1 for extract_insert" +gdb_test_no_output "set \$xmm2.uint128 = 0xcafe" \ + "set ymm2 for extract_insert" +gdb_test_no_output "set \$ymm15.v2_int128 = {0,0}" "set ymm15 for extract_insert" +gdb_test_no_output "set \$rax = 0" "set eax for extract_insert" + +if {[record_full_function "extract_insert"] == true} { + test_one_memory "vpextrw" "global_buf1" \ + "\\\{0x1, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0, 0x18" + test_one_general_register "vpextrw" "rax" "0x8000700060005" + + test_one_register "vpinsrq" "ymm15" \ + "0x50000cafe, 0x0" + test_one_register "vpinsrd" "ymm0" \ + "0x500cafe, 0x0" + test_one_register "vpinsrw" "ymm15" \ + "0x80007000600050004000300020001, 0x0" + test_one_register "vpinsrb" "ymm0" \ + "0x80007000600050000cafe00020001, 0x0" + + test_one_memory "vpextrq" "global_buf1" \ + "\\\{0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18" + test_one_general_register "vpextrq" "rax" "0x80007" + test_one_memory "vpextrd" "global_buf1" \ + "\\\{0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18" + test_one_general_register "vpextrd" "rax" "0x0" + test_one_memory "vpextrb" "global_buf1" \ + "\\\{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18" + test_one_general_register "vpextrb" "rax" "0xcafe" + + test_one_general_register "vextractps" "eax" "0x0" + test_one_register "vinsertps" "ymm0" \ + "0x10000f000e000d000c000b000a0009, 0x0" + test_one_register "vextractf128" "ymm15" \ + "0xcafe, 0x10000f000e000d000c000b000a0009" + test_one_register "vextracti128" "ymm0" \ + "0x80007000600050004000300020001, 0xcafe" + test_one_register "vinsertf128" "ymm15" \ + "0x0, 0x0" + test_one_register "vinserti128" "ymm0" \ + "0x0, 0x0" + + gdb_test "record stop" "Process record is stopped.*" \ + "delete history for extract_insert_test" +} else { + untested "couldn't run extract_insert tests" +} +gdb_test "finish" "Run till exit from.*extract_insert_test.*" \ + "leaving extract_insert"