@@ -207,6 +207,12 @@ static int mem_read16(const i386_image_t *img, uint32_t va, uint16_t *out) {
207207 * out = (uint16_t )((uint16_t )p [0 ] | ((uint16_t )p [1 ] << 8 ));
208208 return 1 ;
209209}
210+ // Byte write (E5 r9 — for CMPXCHG r/m8,r8 write-back).
211+ static int mem_write8 (const i386_image_t * img , uint32_t va , uint8_t val ) {
212+ if (va < img -> base || (uint64_t )va + 1 > (uint64_t )img -> base + img -> size ) return 0 ;
213+ img -> host [va - img -> base ] = val ;
214+ return 1 ;
215+ }
210216
211217// Compute the effective address for a memory-form ModR/M (mod != 3).
212218// Supports the [reg+disp] / [disp32] forms the entry block uses; SIB is
@@ -248,6 +254,25 @@ static int rm_get8(const i386_cpu_t *cpu, const i386_image_t *img,
248254 return 1 ;
249255}
250256
257+ // Write an 8-bit value to the r/m operand. Register-direct: same byte-
258+ // register map as rm_get8 (rm<4 = low byte of gpr[rm]; rm>=4 = high byte
259+ // of gpr[rm-4] / ah/ch/dh/bh). (E5 r9 — for CMPXCHG r/m8,r8 write-back.)
260+ static int rm_set8 (i386_cpu_t * cpu , const i386_image_t * img ,
261+ const i386_insn_t * insn , uint8_t val , i386_halt_t * why ) {
262+ int mod = (insn -> modrm >> 6 ) & 3 ;
263+ if (mod == 3 ) {
264+ int rm = insn -> modrm & 7 ;
265+ if (rm < 4 ) cpu -> gpr [rm ] = (cpu -> gpr [rm ] & ~(uint32_t )0xFFu ) | (uint32_t )val ;
266+ else cpu -> gpr [rm - 4 ] = (cpu -> gpr [rm - 4 ] & ~(uint32_t )0xFF00u )
267+ | ((uint32_t )val << 8 );
268+ return 1 ;
269+ }
270+ uint32_t a ;
271+ if (!i386_ea (cpu , insn , & a )) { * why = I386_HALT_UNSUPPORTED ; return 0 ; }
272+ if (!mem_write8 (img , a , val )) { * why = I386_HALT_OOB ; return 0 ; }
273+ return 1 ;
274+ }
275+
251276// Read a 16-bit r/m operand. Register-direct = low word of gpr[rm].
252277static int rm_get16 (const i386_cpu_t * cpu , const i386_image_t * img ,
253278 const i386_insn_t * insn , uint16_t * val , i386_halt_t * why ) {
@@ -776,6 +801,65 @@ void i386_cpu_run(i386_cpu_t *cpu, const i386_image_t *img,
776801 }
777802 break ;
778803 }
804+ // ── CMPXCHG r/m32,r32 (E5 r9) — 0F B1 /r ─────────────────────────────
805+ // Compare EAX with r/m32; if equal: ZF=1, r/m32←reg32; else ZF=0,
806+ // EAX←r/m32. Flags set per alu_sub (CF/OF/AF/SF/ZF/PF). Intel SDM
807+ // Vol.2 CMPXCHG. Plain lock-free CRT init primitive — no protection.
808+ case I386_OP_CMPXCHG_RM_R : {
809+ int reg = (insn .modrm >> 3 ) & 7 ; // the "new value" source register
810+ i386_halt_t why = I386_HALT_UNSUPPORTED ;
811+ uint32_t rm_v ;
812+ if (!rm_get32 (cpu , img , & insn , & rm_v , & why )) {
813+ res -> halt = why ; res -> halt_va = eip ; res -> halt_op = insn .op ; return ;
814+ }
815+ uint32_t eax_v = cpu -> gpr [I386_REG_EAX ];
816+ (void )alu_sub (& cpu -> eflags , eax_v , rm_v , 0 ); // flags per SDM CMP
817+ if (eax_v == rm_v ) {
818+ // ZF already set to 1 by alu_sub (result==0); write reg into r/m
819+ if (!rm_set32 (cpu , img , & insn , cpu -> gpr [reg ], & why )) {
820+ res -> halt = why ; res -> halt_va = eip ; res -> halt_op = insn .op ; return ;
821+ }
822+ } else {
823+ // ZF already 0; EAX ← r/m32
824+ cpu -> gpr [I386_REG_EAX ] = rm_v ;
825+ }
826+ break ;
827+ }
828+ // ── CMPXCHG r/m8,r8 (E5 r9) — 0F B0 /r ──────────────────────────────
829+ // Compare AL with r/m8; if equal: ZF=1, r/m8←reg8; else ZF=0, AL←r/m8.
830+ // Flags set per 8-bit alu_sub semantics. Intel SDM Vol.2 CMPXCHG.
831+ case I386_OP_CMPXCHG_RM8_R8 : {
832+ int reg = (insn .modrm >> 3 ) & 7 ; // 8-bit "new value" source reg
833+ i386_halt_t why = I386_HALT_UNSUPPORTED ;
834+ uint8_t rm_v8 ;
835+ if (!rm_get8 (cpu , img , & insn , & rm_v8 , & why )) {
836+ res -> halt = why ; res -> halt_va = eip ; res -> halt_op = insn .op ; return ;
837+ }
838+ uint8_t al = (uint8_t )(cpu -> gpr [I386_REG_EAX ] & 0xFFu );
839+ // Compute 8-bit flags via 32-bit alu_sub (zero-extended operands),
840+ // then fixup SF and PF for the 8-bit result width.
841+ (void )alu_sub (& cpu -> eflags , (uint32_t )al , (uint32_t )rm_v8 , 0 );
842+ uint8_t r8 = (uint8_t )(al - rm_v8 );
843+ set_flag (& cpu -> eflags , EFL_SF , (int )((r8 >> 7 ) & 1u )); // 8-bit sign
844+ { int ones8 = 0 ;
845+ uint8_t pr = r8 ;
846+ for (int ii = 0 ; ii < 8 ; ii ++ ) ones8 += (pr >> ii ) & 1 ;
847+ set_flag (& cpu -> eflags , EFL_PF , (ones8 & 1 ) == 0 ); }
848+ if (al == rm_v8 ) {
849+ // Get reg8 value (same byte-reg map as rm_get8)
850+ uint8_t reg_v8 ;
851+ if (reg < 4 ) reg_v8 = (uint8_t )(cpu -> gpr [reg ] & 0xFFu );
852+ else reg_v8 = (uint8_t )((cpu -> gpr [reg - 4 ] >> 8 ) & 0xFFu );
853+ if (!rm_set8 (cpu , img , & insn , reg_v8 , & why )) {
854+ res -> halt = why ; res -> halt_va = eip ; res -> halt_op = insn .op ; return ;
855+ }
856+ } else {
857+ // AL ← r/m8
858+ cpu -> gpr [I386_REG_EAX ] = (cpu -> gpr [I386_REG_EAX ] & 0xFFFFFF00u )
859+ | (uint32_t )rm_v8 ;
860+ }
861+ break ;
862+ }
779863 // ── E4 kernel32 boundary — indirect IAT call / jump ───────────
780864 case I386_OP_CALL_RM : // FF /2 [..]
781865 case I386_OP_JMP_RM : { // FF /4 [..]
@@ -790,12 +874,15 @@ void i386_cpu_run(i386_cpu_t *cpu, const i386_image_t *img,
790874 }
791875 uint32_t slot_va = (uint32_t )insn .disp ;
792876 const i386_import_t * imp = i386_iat_lookup (cpu -> iat , slot_va );
793- if (!imp ) {
794- // Slot is an IAT thunk but no binding is registered — the
795- // next kernel32 import to implement. own1: we never invent
796- // the function; we stop and name the slot.
877+ if (!imp || !imp -> fn ) {
878+ // Slot is an IAT thunk but no binding is registered (imp==NULL),
879+ // OR the import was found by name but has no shim (fn==NULL from
880+ // autobind for an unknown import). own1: we never invent the
881+ // function; we stop honestly and record the slot + name if known.
797882 res -> halt = I386_HALT_UNBOUND_IMPORT ; res -> halt_va = eip ;
798- res -> halt_op = insn .op ; res -> import_slot = slot_va ; return ;
883+ res -> halt_op = insn .op ; res -> import_slot = slot_va ;
884+ if (imp && imp -> name ) res -> last_import = imp -> name ; // name from autobind
885+ return ;
799886 }
800887 // BIND: dispatch to the native shim, place the result in EAX
801888 // (Win32 ABI), pop stdcall callee-popped args, and continue.
@@ -875,6 +962,102 @@ int i386_cpu_load_pe(const char *path, i386_image_t *out, uint32_t *entry_va) {
875962 return 0 ;
876963}
877964
965+ // ── IAT name-based autobind (E5 r9) ─────────────────────────────────────────
966+ // Walk the PE Import Directory in `img` at RVA `import_dir_rva` and match
967+ // each import name against the name-keyed registry. Writes entries to
968+ // bound_out[]; fn==NULL entries mark unresolved names (honest: the run loop
969+ // halts UNBOUND_IMPORT + records last_import=name for those). Returns total
970+ // imports seen; *bound_count_out = resolved (fn!=NULL); *unbound_count_out =
971+ // unresolved (fn==NULL). own1: standard PE import resolution over OUR sections
972+ // — binding our OWN imports to native impls is LOADING, not a bypass. No Wine.
973+ static const char * img_cstr (const i386_image_t * img , uint32_t va ) {
974+ // Return a pointer to a C string in the flat image at `va`, or NULL if OOB.
975+ if (va < img -> base || va >= img -> base + img -> size ) return NULL ;
976+ return (const char * )(img -> host + (va - img -> base ));
977+ }
978+
979+ int i386_iat_autobind (
980+ const struct i386_image * img ,
981+ uint32_t import_dir_rva ,
982+ const i386_shim_entry_t * registry ,
983+ uint32_t reg_count ,
984+ i386_import_t * bound_out ,
985+ uint32_t max_bound ,
986+ uint32_t * bound_count_out ,
987+ uint32_t * unbound_count_out )
988+ {
989+ uint32_t n_total = 0 , n_bound = 0 , n_unbound = 0 ;
990+ if (!img || !img -> host || import_dir_rva == 0 ) {
991+ if (bound_count_out ) * bound_count_out = 0 ;
992+ if (unbound_count_out ) * unbound_count_out = 0 ;
993+ return 0 ;
994+ }
995+ uint32_t base = img -> base ;
996+ uint32_t desc_va = base + import_dir_rva ;
997+
998+ // Walk IMAGE_IMPORT_DESCRIPTOR array (20 bytes each, null-terminated).
999+ for (;;) {
1000+ uint32_t orig_first_thunk , unused1 , unused2 , name_rva , first_thunk ;
1001+ if (!i386_mem_read32 (img , desc_va + 0 , & orig_first_thunk )) break ;
1002+ if (!i386_mem_read32 (img , desc_va + 4 , & unused1 )) break ;
1003+ if (!i386_mem_read32 (img , desc_va + 8 , & unused2 )) break ;
1004+ if (!i386_mem_read32 (img , desc_va + 12 , & name_rva )) break ;
1005+ if (!i386_mem_read32 (img , desc_va + 16 , & first_thunk )) break ;
1006+ (void )unused1 ; (void )unused2 ;
1007+ // Null terminator: all-zero descriptor (valid entry always has name+thunk).
1008+ if (orig_first_thunk == 0 && name_rva == 0 && first_thunk == 0 ) break ;
1009+
1010+ // Use OriginalFirstThunk (INT) when present, else FirstThunk (IAT).
1011+ // Both hold RVAs to IMAGE_IMPORT_BY_NAME pre-binding.
1012+ uint32_t int_va = base + (orig_first_thunk ? orig_first_thunk : first_thunk );
1013+ uint32_t iat_va = base + first_thunk ;
1014+
1015+ // Walk INT in lockstep with IAT slots (4 bytes per entry for PE32).
1016+ for (uint32_t si = 0 ; ; si ++ ) {
1017+ uint32_t int_entry ;
1018+ if (!i386_mem_read32 (img , int_va + si * 4 , & int_entry )) break ;
1019+ if (int_entry == 0 ) break ; // null terminator
1020+
1021+ uint32_t slot_va = iat_va + si * 4 ;
1022+ const i386_shim_entry_t * found = NULL ;
1023+ const char * imp_name = NULL ;
1024+
1025+ if (int_entry & 0x80000000u ) {
1026+ // Ordinal import (bit31=1) — not shimmed by name.
1027+ imp_name = "(ordinal)" ;
1028+ } else {
1029+ // Named import: IMAGE_IMPORT_BY_NAME at base+int_entry.
1030+ // Skip 2-byte Hint; name follows immediately.
1031+ uint32_t ibn_va = base + int_entry ;
1032+ imp_name = img_cstr (img , ibn_va + 2 );
1033+ if (imp_name ) {
1034+ for (uint32_t r = 0 ; r < reg_count ; r ++ ) {
1035+ if (strcmp (imp_name , registry [r ].name ) == 0 ) {
1036+ found = & registry [r ];
1037+ break ;
1038+ }
1039+ }
1040+ }
1041+ }
1042+
1043+ if (n_total < max_bound ) {
1044+ bound_out [n_total ].slot_va = slot_va ;
1045+ bound_out [n_total ].name = found ? found -> name
1046+ : (imp_name ? imp_name : "?" );
1047+ bound_out [n_total ].fn = found ? found -> fn : NULL ;
1048+ bound_out [n_total ].arg_bytes = found ? found -> arg_bytes : 0 ;
1049+ }
1050+ n_total ++ ;
1051+ if (found ) n_bound ++ ;
1052+ else n_unbound ++ ;
1053+ }
1054+ desc_va += 20 ; // next descriptor
1055+ }
1056+ if (bound_count_out ) * bound_count_out = n_bound ;
1057+ if (unbound_count_out ) * unbound_count_out = n_unbound ;
1058+ return (int )n_total ;
1059+ }
1060+
8781061void i386_image_free (i386_image_t * img ) {
8791062 if (img && img -> owns && img -> host ) {
8801063 free (img -> host );
0 commit comments