modrm11.s 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. # modrm11.s -- establish the mod=11 half of the 16-bit ModR/M table using
  2. # GNU as as an independent ENCODER.
  3. #
  4. # modrm19.s measures the memory forms (mod=00/01/10) by executing them on a
  5. # real 8086 under qemu: it stores a marker through each encoding and reports
  6. # which physical address received it. mod=11 is not a memory form at all --
  7. # the r/m field names a register -- so it needs a different oracle, and this
  8. # file is it.
  9. #
  10. # Why the assembler rather than another qemu probe
  11. # ------------------------------------------------
  12. # The obvious extension of modrm19.s is "store into the register, then report
  13. # which register took it". That does not work, and the reason is worth
  14. # recording because it is a trap rather than a puzzle:
  15. #
  16. # * The comparison needs a register to hold the expected value, and every
  17. # register is a candidate, so the check clobbers what it is measuring.
  18. # * r/m=100 is SP, and the marker value is not a legal stack pointer. The
  19. # very next `call` pushes its return address through SS:BEEF and leaves
  20. # SP at BEED, so by the time any check runs, the evidence is gone. A
  21. # probe that reports "not found" for SP is reporting its own design, not
  22. # the hardware. (This is not hypothetical: the first version of that
  23. # probe did exactly this, and also cleared AX "because AX is never an
  24. # r/m target" -- which is true of the mod=11 list people remember, and
  25. # false of the real one, where r/m 000 IS AX. It reported two
  26. # impossible answers and a plausible-looking third.)
  27. # * Repairing that needs the check inlined between the store and the next
  28. # call, so every case gets its own hand-written code block and its own
  29. # chance of a typo -- and a typo there produces a plausible wrong answer,
  30. # which is the worst kind.
  31. #
  32. # The assembler has none of those problems. `as --32` with `.code16` is a
  33. # correct 16-bit x86 *encoder*, and it was already the encoder half of the
  34. # FCML cross-validation (38/38 agreement on a separate probe). Asking it to
  35. # encode `movw %bx, %si` and getting `89 DE` back is a direct, unambiguous
  36. # statement that in mod=11 the r/m field 110 means SI.
  37. #
  38. # So the two files are complementary halves of one argument.
  39. # modrm19.s execution -> mod=00/01/10 effective addresses
  40. # this file encoding -> mod=11 register identities
  41. # Nothing in the table is taken from memory, and the two oracles share no code.
  42. #
  43. # This file is in four groups, and modrm11.py checks all four differently:
  44. #
  45. # 1. the r/m column 8 x `movw %bx, <reg>` -> low three bits of ModRM
  46. # 2. the reg column 8 x `movw <reg>, %di` -> bits 5..3 of ModRM
  47. # 3. the byte list 6 x 8-bit moves -> AL CL DL BL AH CH DH BH
  48. # 4. the anchors 4 hand-checking bytes -> the table itself
  49. #
  50. # Group 4 is what makes the check non-vacuous. Groups 1-3 are self
  51. # referential in a dangerous way: they compare this file against the
  52. # assembler, so editing this file just changes the claim and the assembler
  53. # faithfully re-encodes it. A check like that cannot fail on a wrong table
  54. # unless the table is what moved. The anchors are different -- they are
  55. # encodings nobody types by hand, so they carry information this file's
  56. # author did not supply, and a table shifted by one position cannot satisfy
  57. # them.
  58. #
  59. # Encode and check (see modrm11.py):
  60. # as --32 -o modrm11.o modrm11.s
  61. # objcopy -O binary -j .text modrm11.o modrm11.bin
  62. .code16
  63. .text
  64. # --- 1. the r/m field, read straight off the low three bits -------------
  65. # Each of these is `movw %bx, <reg>`, i.e. opcode 89 with reg=BX (011), so
  66. # ModRM = 11 011 rrr and the low three bits ARE the r/m code for the register
  67. # named on the right. One instruction per r/m code, in r/m order. Opcode 89
  68. # is MOV r/m,r, so the r/m field is the DESTINATION -- the register on the
  69. # right of the AT&T line. Both of those directions matter, and getting
  70. # either backwards produces a table that is wrong everywhere.
  71. movw %bx, %cx # r/m 000
  72. movw %bx, %dx # r/m 010
  73. movw %bx, %bx # r/m 011
  74. movw %bx, %sp # r/m 100
  75. movw %bx, %bp # r/m 101
  76. movw %bx, %si # r/m 110
  77. movw %bx, %di # r/m 111
  78. movw %bx, %bx # r/m 011 again -- there is no second BX
  79. # --- 2. the reg field, read off bits 5..3 ------------------------------
  80. # `movw <reg>, %di` is opcode 89 with rm=DI (111), so ModRM = 11 rrr 111 and
  81. # bits 5..3 are the r/g code, which is the SOURCE. All eight codes are
  82. # encodable, AX included: 89 with reg=AX and mod=11 is an ordinary
  83. # MOV r/m,r, not an accumulator short form -- unlike ADD/ADC/AND/OR/SBB/SUB/
  84. # XOR/CMP, where /0 means the accumulator and the ModRM byte does collapse.
  85. movw %ax, %di # reg 000
  86. movw %cx, %di # reg 001
  87. movw %dx, %di # reg 010
  88. movw %bx, %di # reg 011
  89. movw %sp, %di # reg 100
  90. movw %bp, %di # reg 101
  91. movw %si, %di # reg 110
  92. movw %di, %di # reg 111
  93. # --- 3. the 8-bit forms, where the list differs and direction flips -----
  94. # 88 /r is MOV r/m8,r8 (reg is the SOURCE); 8A /r is MOV r8,r/m8 (reg is the
  95. # DESTINATION). Both use the identical ModRM byte for the same pair of
  96. # registers, so the byte alone cannot tell you the direction -- the opcode
  97. # can. The 8-bit list is also its own: AL CL DL BL AH CH DH BH, which agrees
  98. # with the word list at every code except 100, where it is AH rather than
  99. # SP. This is the other trap in the table, and Runtime.mod documents it.
  100. movb %al, %dh # 88 C6 -> DH := AL
  101. movb %dh, %al # 88 F0 -> AL := DH
  102. movb %al, %dl # 88 C2 -> DL := AL
  103. movb %dl, %al # 88 D0 -> AL := DL
  104. movb %al, %bl # 88 C3 -> BL := AL
  105. movb %bl, %al # 88 D8 -> AL := BL
  106. # --- 4. the anchors -----------------------------------------------------
  107. # Four instructions no one writes by hand, each of which pins one cell of the
  108. # table. If the ModRM column in modrm11.py is shifted by one, every one of
  109. # these disagrees with it. They are the reason this file can fail for a
  110. # reason other than "someone edited the claims".
  111. #
  112. # They are spelled as literal bytes, not as mnemonics, because the point is
  113. # the exact sequence: `as` picks opcode 89 rather than 8B for a
  114. # register-to-register move, so asking it for `movw %sp, %bp` gets 89 E5 and
  115. # never 8B EC. Both mean MOV BP,SP and the two differ in which field holds
  116. # which register -- which is exactly what the anchor is here to pin.
  117. # modrm11.py supplies the expected decode for each, so a byte that does not
  118. # decode as its comment claims still fails.
  119. .byte 0x83, 0xC4, 0x08 # ADD SP, 8 rm=100 -> SP
  120. .byte 0x83, 0xC6, 0x02 # ADD SI, 2 rm=110 -> SI
  121. .byte 0x8B, 0xEC # MOV BP, SP reg=101 rm=100
  122. .byte 0x8B, 0xE5 # MOV SP, BP reg=100 rm=101