Emuluj procesor Intel 8086


157

Uwaga: nadeszło kilka odpowiedzi. Zastanów się też nad poprawieniem nowszych odpowiedzi.


8086 to pierwszy mikroprocesor Intel x86. Twoim zadaniem jest napisanie emulatora. Ponieważ jest to stosunkowo zaawansowane, chcę go ograniczyć:

  • Należy zaimplementować tylko następujące kody operacyjne:
    • mov, push, pop, xchg
    • add, adc, sub, sbb, cmp i lub lub xor
    • inc, dec
    • call, ret, jmp
    • jb, jz, jbe, js, jnb, jnz, jnbe, jns
    • stc, clc
    • hlt, nop
  • W rezultacie musisz tylko obliczyć flagi carry, zero i sign
  • Nie implementuj segmentów. Załóżmy cs = ds = ss = 0.
  • Brak prefiksów
  • Żadnych rodzajów przerwań ani portów IO
  • Brak funkcji ciągów
  • Brak dwubajtowych kodów operacyjnych (0F ..)
  • Brak arytmetyki zmiennoprzecinkowej
  • (oczywiście) żadnych rzeczy 32-bitowych, sse, mmx, ... cokolwiek, co nie zostało jeszcze wynalezione w 1979 roku
  • Nie musisz liczyć cykli ani wykonywać żadnych pomiarów czasu

Zacznij od ip = 0i sp = 100h.


Wejście: Twój emulator powinien pobrać program binarny w dowolnym formacie, który ci się podoba (odczyt z pliku, predefiniowana tablica, ...) i załadować go do pamięci pod adresem 0.

Wyjście: RAM wideo rozpoczyna się pod adresem 8000h, każdy bajt ma jeden znak (ASCII-). Emuluj ekran 80x25 do konsoli. Traktuj bajty zerowe jak spacje.

Przykład:

08000   2E 2E 2E 2E 2E 2E 2E 2E 2E 00 00 00 00 00 00 00   ................
08010   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
08050   48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 00 00 00   Hello,.world!...

Uwaga: Jest to bardzo podobne do prawdziwego trybu wideo, który zwykle ma wartość 0xB8000 i ma inny bajt na znak dla kolorów.

Kryteria wygranej:

  • Wszystkie wymienione instrukcje muszą zostać wdrożone
  • Zrobiłem niekomentowany program testowy ( link , źródło nasm ), który powinien działać poprawnie. Wyprowadza

    .........                                                                       
    Hello, world!                                                                   
    0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 
    
    
    ################################################################################
    ##                                                                            ##
    ##  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987                          ##
    ##                                                                            ##
    ##  0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400     ##
    ##                                                                            ##
    ##  2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97    ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ################################################################################
    
  • Nie jestem pewien, czy powinien to być codegolf; to trochę trudne zadanie, więc każde zgłoszenie i tak zyska wiele pozytywnych opinii. Proszę skomentuj.

Oto kilka linków, które pomogą ci w tym zadaniu:

To mój pierwszy wpis na tę platformę. Jeśli są jakieś błędy, proszę je wskazać; jeśli przegapiłem jakiś szczegół, po prostu zapytaj.


5
jest dla mnie zdecydowanie zbyt zaawansowany, ale bardzo chętnie znajduję odpowiedzi na to pytanie, ponieważ to właśnie takie rzeczy najbardziej mnie interesują! Mogę się tym zająć później, jeśli czuję się szczególnie masochistyczny ...
Chris Browne

3
@ChrisBrowne powodzenia jest masochistyczny! Obecnie zmieniam swój 8086 w 80386 i do tej pory wiele się nauczyłem z tego projektu.
skopiuj

2
+1 + ulubione ... nie mogę zacząć wyrażać uczuć, które miałem, gdy zobaczyłem to pytanie.
ixtmixilix

2
@ kopiowanie Nigdy nie jest za późno na zawody w golfa dla każdej pary języków / gospodarzy
Yauhen Yakimovich,

2
@ MartinBüttner Oczywiście, pytanie jest starsze niż ten tag i tak naprawdę było to konkurs popularności
skopiuj

Odpowiedzi:


84

Zapraszam do widelca i gry w golfa: https://github.com/julienaubert/py8086

Wynik Dołączyłem również interaktywny debugger.

CF:0 ZF:0 SF:0 IP:0x0000
AX:0x0000  CX:0x0000  DX:0x0000  BX:0x0000  SP:0x0100  BP:0x0000  SI:0x0000  DI:0x0000
AL:  0x00  CL:  0x00  DL:  0x00  BL:  0x00  AH:  0x00  CH:  0x00  DH:  0x00  BH:  0x00
stack: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
cmp SP, 0x100
[Enter]:step [R]:run [B 0xadr]:add break [M 0xadr]:see RAM [Q]:quit

B 0x10
M 0x1
M 0x1: 0xfc 0x00 0x01 0x74 0x01 0xf4 0xbc 0x00 0x10 0xb0 0x2e 0xbb ...
R

CF:0 ZF:0 SF:1 IP:0x0010
AX:0x002e  CX:0x0000  DX:0x0000  BX:0xffff  SP:0x1000  BP:0x0000  SI:0x0000  DI:0x0000
AL:  0x2e  CL:  0x00  DL:  0x00  BL:  0xff  AH:  0x00  CH:  0x00  DH:  0x00  BH:  0x00
stack: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ...
cmp BX, 0xffff
[Enter]:step [R]:run [B 0xadr]:add break [M 0xadr]:see RAM [Q]:quit

Istnieją trzy pliki: emu8086.py (wymagane) console.py (opcjonalne dla wyjścia wyświetlania), disasm.py (opcjonalne, aby uzyskać listę asm w codegolf).

Aby uruchomić z wyświetlaczem (uwaga używa przekleństw):

python emu8086.py 

Aby uruchomić z interaktywnym debuggerem:

python emu8086.py a b

Aby uruchomić z nieinteraktywnym „debuggerem”:

python emu8086.py a

Program „ codegolf ” powinien znajdować się w tym samym katalogu.

emu8086.py

console.py

disasm.py

Na githubie


9
To piekielnie pierwszy post Code Golf. +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 ...
Dillon Cower

@DC dzięki :) było fajnym wyzwaniem!
ja

1
Nadal nie mogę uwierzyć, że ktoś to zrobił :-) Świetna robota!
skopiuj

1
Niesamowity! Gratulacje! Ile linii miał w końcu?
Will Lp

59

Haskell, 256 234 196 linii

Już od jakiegoś czasu pracuję nad tym, co było w toku, chciałem go jeszcze trochę dopracować przed opublikowaniem, ale teraz oficjalnie zaczęła się zabawa, nie ma już sensu ukrywać go. Zauważyłem podczas wydobywania, że ​​ma dokładnie 256 linii, więc przypuszczam, że jest w „niezwykłym” punkcie swojego istnienia.

Co znajduje się: ledwo wystarczy zestawu instrukcji 8086, aby bezbłędnie uruchomić przykładowy plik binarny. Obsługiwany jest kod samomodyfikujący. (pobieranie wstępne: zero bajtów)
Jak na ironię, pierwsze wystarczające iteracje kodu były dłuższe i obsługiwały mniejszy zakres opcode. Refaktoryzacja okazała się korzystna zarówno pod względem długości kodu, jak i zasięgu operacji.

Co się dzieje: oczywiście segmenty, prefiksy i wielobajtowe kody, przerwania, porty I / O, operacje na łańcuchach i FP. Początkowo postępowałem zgodnie z pierwotnym PUSH SPzachowaniem, ale musiałem go porzucić po kilku iteracjach.

Wyniki flagi przenoszenia są prawdopodobnie bardzo pomieszane w kilku przypadkach ADC/ SBB.

W każdym razie oto kod:

------------------------------------------------------------
-- Imports

-- They're the only lines I allow to go over 80 characters.
-- For the simple reason the code would work just as well without the
-- actual symbol list, but I like to keep it up to date to better
-- grasp my dependency graph.

import           Control.Monad.Reader      (ReaderT,runReaderT,ask,lift,forever,forM,when,void)
import           Control.Monad.ST          (ST,runST)
import           Control.Monad.Trans.Maybe (MaybeT,runMaybeT)
import           Data.Array.ST             (STUArray,readArray,writeArray,newArray,newListArray)
import           Data.Bits                 (FiniteBits,(.&.),(.|.),xor,shiftL,shiftR,testBit,finiteBitSize)
import           Data.Bool                 (bool)
import qualified Data.ByteString as B      (unpack,getContents)
import           Data.Char                 (chr,isPrint) -- for screen dump
import           Data.Int                  (Int8)
import           Data.STRef                (STRef,newSTRef,readSTRef,writeSTRef,modifySTRef)
import           Data.Word                 (Word8,Word16)

------------------------------------------------------------
-- Bytes and Words
-- Bytes are 8 bits.  Words are 16 bits.  Addressing is little-endian.

-- Phantom types.  Essentially (only?) used for the ALU
byte = undefined :: Word8
word = undefined :: Word16

-- Byte to word conversion
byteToWordSE = (fromIntegral :: Int8 -> Word16) .
               (fromIntegral :: Word8 -> Int8)

-- Two-bytes to word conversion
concatBytes :: Word8 -> Word8 -> Word16
concatBytes l h = fromIntegral l .|. (fromIntegral h `shiftL` 8)

-- Word to two bytes conversion
wordToByteL,wordToByteH :: Word16 -> Word8
wordToByteL = fromIntegral
wordToByteH = fromIntegral . (`shiftR` 8)

-- A Place is an lvalue byte or word.  In absence of I/O ports, this
-- means RAM or register file.  This type synonym is not strictly
-- needed, but without it it's unclear I could keep the alu function
-- type signature under twice 80 characters, so why not keep this.
type Place s = (STUArray s Word16 Word8,Word16)

-- Read and write, byte or word, from RAM or register file

class (Ord a,FiniteBits a,Num a) => Width a where
  readW  :: Place s ->      MonadCPU s a
  writeW :: Place s -> a -> MonadCPU s ()

instance Width Word8 where
  readW  =  liftST    . uncurry readArray
  writeW = (liftST .) . uncurry writeArray

instance Width Word16 where
  readW (p,a) = concatBytes <$> readW (p,a) <*> readW (p,a+1)
  writeW (p,a) val = do
    writeW (p,a)   $ wordToByteL val
    writeW (p,a+1) $ wordToByteH val

------------------------------------------------------------
-- CPU object

-- The actual CPU state.  Yeah, I obviously don't have all flags in! :-D
data CPU s = CPU { ram  :: STUArray s Word16 Word8
                 , regs :: STUArray s Word16 Word8
                 , cf :: STRef s Bool
                 , zf :: STRef s Bool
                 , sf :: STRef s Bool }

newCPU rawRam = do ramRef <- newListArray (0,0xFFFF) rawRam
                   regFile <- newArray (0,17) 0
                   cf <- newSTRef False
                   zf <- newSTRef False
                   sf <- newSTRef False
                   return $ CPU ramRef regFile cf zf sf

-- Register addresses within the register file.  Note odd placement
-- for BX and related.  Also note the 16-bit registers have a wider
-- pitch.  IP was shoehorned in recently, it doesn't really need an
-- address here, but it made other code shorter, so that's that.

-- In the 8-bit subfile, only regAl is used in the code (and it's 0,
-- so consider that a line I could totally have skipped)
[regAl,regAh,regCl,regCh,regDl,regDh,regBl,regBh] = [0..7]

-- In the 16-bit file, they're almost if not all referenced.  8086
-- sure is clunky.
[regAx,regCx,regDx,regBx,regSp,regBp,regSi,regDi,regIp] = [0,2..16]

-- These functions look like I got part of the Lens intuition
-- independently, come to look at it after the fact.  Cool :-)
readCpu  ext   = liftST .      readSTRef    . ext =<< ask
writeCpu ext f = liftST . flip writeSTRef f . ext =<< ask

-- It looks like the only operations IP can receive are relative moves
-- (incrIP function below) and a single absolute set: RET.  I deduce
-- only short jumps, not even near, were in the spec.
incrIP i = do old <- readReg regIp
              writeReg regIp (old + i)
              return old

-- Read next instruction.  Directly from RAM, so no pipeline prefetch.
readInstr8 = incrIP 1 >>= readRam
readInstr16 = concatBytes <$> readInstr8 <*> readInstr8

-- RAM/register file R/W specializers
readReg  reg      = ask >>= \p -> readW  (regs p,reg)
readRam  addr     = ask >>= \p -> readW  (ram p ,addr)
writeReg reg val  = ask >>= \p -> writeW (regs p,reg)  val
writeRam addr val = ask >>= \p -> writeW (ram p ,addr) val

-- I'm not quite sure what those do anymore, or why they're separate.
decodeReg8  n = fromIntegral $ (n `shiftL` 1) .|. (n `shiftR` 2)
decodeReg16 n = fromIntegral $  n `shiftL` 1
readDecodedReg8 = readReg . decodeReg8
readDecodedReg16 = readReg . decodeReg16

-- The monad type synonym make type signatures easier :-(
type MonadCPU s = MaybeT (ReaderT (CPU s) (ST s))

-- Specialized liftST, because the one from Hackage loses the
-- parameter, and I need it to be able to qualify Place.
liftST :: ST s a -> MonadCPU s a
liftST = lift . lift

------------------------------------------------------------
-- Instructions

-- This is arguably the core secret of the 8086 architecture.
-- See statement links for actual explanations.
readModRM = do
  modRM <- readInstr8
  let mod   =  modRM           `shiftR` 6
      opReg = (modRM .&. 0x38) `shiftR` 3
      rm    =  modRM .&. 0x07
  cpu <- ask
  operand <- case mod of
               0 -> do
                 addr <- case rm of
                           1 -> (+) <$> readReg regBx <*> readReg regDi
                           2 -> (+) <$> readReg regBp <*> readReg regSi
                           6 -> readInstr16
                           7 -> readReg regBx
                 return (ram cpu,addr)
               2 -> do
                 addr <- case rm of
                           5 -> (+) <$> readReg regDi <*> readInstr16
                           7 -> (+) <$> readReg regBx <*> readInstr16
                 return (ram cpu,addr)
               3 -> return (regs cpu,2*fromIntegral rm)
  return (operand,opReg,opReg)

-- Stack operations.  PUSH by value (does NOT reproduce PUSH SP behavior)
push16 val = do
  sp <- subtract 2 <$> readReg regSp
  writeReg regSp sp
  writeRam sp (val :: Word16)
pop16 = do
  sp <- readReg regSp
  val <- readRam sp
  writeReg regSp (sp+2)
  return (val :: Word16)

-- So, yeah, JMP seems to be relative (short) only.  Well, if that's enough…
jump cond = when cond . void . incrIP . byteToWordSE =<< readInstr8

-- The ALU.  The most complicated type signature in this file.  An
-- initial argument as a phantom type I tried to get rid of and
-- failed.
alu :: Width w => w -> MonadCPU s w -> MonadCPU s w -> Place s
    -> (w -> w -> MonadCPU s (Bool,Maybe Bool,w)) -> MonadCPU s ()
alu _ a b r op = do
  (rw,c,v) <- a >>= (b >>=) . op
  when rw $ writeW r v
  maybe (return ()) (writeCpu cf) c
  writeCpu zf (v == 0)
  writeCpu sf (testBit v (finiteBitSize v - 1))
decodeALU 0 = \a b -> return (True, Just (a >= negate b),       a   +   b)
decodeALU 1 = \a b -> return (True, Just False,                 a  .|.  b)
decodeALU 2 = \a b -> bool 0 1 <$> readCpu cf >>= \c ->
                      return (True, Just (a >= negate (b + c)), a + b + c)
decodeALU 3 = \a b -> bool 0 1 <$> readCpu cf >>= \c ->
                      return (True, Just (a < b + c),           a - b - c)
decodeALU 4 = \a b -> return (True, Just False,                 a  .&.  b)
decodeALU 5 = \a b -> return (True, Just (a <= b),              a   -   b)
decodeALU 6 = \a b -> return (True, Just False,                 a `xor` b)
decodeALU 7 = \a b -> return (False,Just (a <= b),              a   -   b)
opIncDec :: Width w => w -> w -> MonadCPU s (Bool,Maybe Bool,w)
opIncDec    = \a b -> return (True, Nothing,                    a   +   b)

-- Main iteration: process one instuction
-- That's the rest of the meat, but that part's expected.
processInstr = do
  opcode <- readInstr8
  regs <- regs <$> ask
  let zReg = (regs,decodeReg16 (opcode .&. 0x07))
  if opcode < 0x40 then -- no segment or BCD
    let aluOp = (opcode .&. 0x38) `shiftR` 3 in case opcode .&. 0x07 of
    0 -> do
      (operand,reg,_) <- readModRM
      alu byte (readW operand) (readDecodedReg8 reg) operand (decodeALU aluOp)
    1 -> do
      (operand,reg,_) <- readModRM
      alu word (readW operand) (readDecodedReg16 reg) operand (decodeALU aluOp)
    4 -> alu byte (readReg regAl) readInstr8 (regs,regAl) (decodeALU aluOp)
  else case opcode .&. 0xF8 of -- 16-bit (mostly) reg ops
    0x40 -> alu word (readW zReg) (return   1 ) zReg opIncDec -- 16b INC
    0x48 -> alu word (readW zReg) (return (-1)) zReg opIncDec -- 16b DEC
    0x50 -> readW zReg >>= push16                       -- 16b PUSH reg
    0x58 -> pop16 >>= writeW zReg                       -- 16b POP reg
    0x90 -> do v1 <- readW zReg                         -- 16b XCHG (or NOP)
               v2 <- readReg regAx
               writeW zReg (v2 :: Word16)
               writeReg regAx (v1 :: Word16)
    0xB0 -> readInstr8  >>= writeW zReg -- (BUG!)       -- 8b MOV reg,imm
    0xB8 -> readInstr16 >>= writeW zReg                 -- 16b MOV reg,imm
    _ -> case bool opcode 0x82 (opcode == 0x80) of
      0x72 -> jump       =<< readCpu cf                 -- JB/JNAE/JC
      0x74 -> jump       =<< readCpu zf                 -- JE/JZ
      0x75 -> jump . not =<< readCpu zf                 -- JNE/JNZ
      0x76 -> jump       =<< (||) <$> readCpu cf <*> readCpu zf -- JBE
      0x77 -> jump . not =<< (||) <$> readCpu cf <*> readCpu zf -- JA
      0x79 -> jump . not =<< readCpu sf                 -- JNS
      0x81 -> do                                        -- 16b arith to imm
        (operand,_,op) <- readModRM
        alu word (readW operand) readInstr16 operand (decodeALU op)
      0x82 -> do                                        -- 8b arith to imm
        (operand,_,op) <- readModRM
        alu byte (readW operand) readInstr8 operand (decodeALU op)
      0x83 -> do                                        -- 16b arith to 8s imm
        (operand,_,op) <- readModRM
        alu word (readW operand) (byteToWordSE <$> readInstr8) operand
            (decodeALU op)
      0x86 -> do                                        -- 8b XCHG reg,RM
        (operand,reg,_) <- readModRM
        v1 <- readDecodedReg8 reg
        v2 <- readW operand
        writeReg (decodeReg8 reg) (v2 :: Word8)
        writeW operand v1
      0x88 -> do                                        -- 8b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg8 reg >>= writeW operand
      0x89 -> do                                        -- 16b MOV RM,reg
        (operand,reg,_) <- readModRM
        readDecodedReg16 reg >>= writeW operand
      0x8A -> do                                        -- 8b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- readW operand
        writeReg (decodeReg8 reg) (val :: Word8)
      0x8B -> do                                        -- 16b MOV reg,RM
        (operand,reg,_) <- readModRM
        val <- readW operand
        writeReg (decodeReg16 reg) (val :: Word16)
      0xC3 -> pop16 >>= writeReg regIp                  -- RET
      0xC7 -> do (operand,_,_) <- readModRM             -- 16b MOV RM,imm
                 readInstr16 >>= writeW operand
      0xE8 -> readInstr16 >>= incrIP >>= push16         -- CALL relative
      0xEB -> jump True                                 -- JMP short
      0xF4 -> fail "Halting and Catching Fire"          -- HLT
      0xF9 -> writeCpu cf True                          -- STC
      0xFE -> do                                        -- 8-bit INC/DEC RM
        (operand,_,op) <- readModRM
        alu byte (readW operand) (return $ 1-2*op) operand
            (\a b -> return (True,Nothing,a+b)) -- kinda duplicate :(

------------------------------------------------------------

main = do
  rawRam <- (++ repeat 0) . B.unpack <$> B.getContents
  putStr $ unlines $ runST $ do
    cpu <- newCPU rawRam
    flip runReaderT cpu $ runMaybeT $ do
      writeReg regSp (0x100 :: Word16)
      forever processInstr

    -- Next three lines is the screen dump extraction.
    forM [0..25] $ \i -> forM [0..79] $ \j -> do
      c <- chr . fromIntegral <$> readArray (ram cpu) (0x8000 + 80*i + j)
      return $ bool ' ' c (isPrint c)

Dane wyjściowe dostarczonej próbki binarnej idealnie pasują do specyfikacji. Wypróbuj za pomocą wywołania, takiego jak:

runhaskell 8086.hs <8086.bin

Większość niezaimplementowanych operacji spowoduje po prostu błąd dopasowania wzorca.

Nadal zamierzam wziąć pod uwagę nieco więcej i zaimplementować rzeczywiste wyjście na żywo za pomocą przekleństw.

Aktualizacja 1: zmniejszono ją do 234 linii. Lepiej uporządkuj kod według funkcjonalności, ponownie dopasuj, co mogłoby być, próbowałem trzymać się 80 kolumn. I wielokrotnie refaktoryzował ALU.

Aktualizacja 2: minęło pięć lat, pomyślałem, że aktualizacja może sprawić, że skompiluje się bezbłędnie na najnowszym GHC. Po drodze:

  • pozbyłem się liftM, liftM2 i tym podobnych. Uwielbiam mieć <$>i <*>w Preludium.
  • Data.Bool i Data.ByteString, trochę oszczędza i czyści.
  • Rejestr IP był kiedyś specjalny (nieadresowalny), teraz znajduje się w pliku rejestru. To nie ma sensu 8086, ale hej jestem golfistą.
  • Teraz wszystko jest oparte na czystym kodzie ST. Z golfowego punktu widzenia jest to do bani, ponieważ wymagało wielu podpisów typu. Z drugiej strony miałem kłótnię z sumieniem i przegrałem, więc teraz dostajesz czysty, długi kod.
  • Teraz jest to śledzone przez Git.
  • Dodano bardziej poważne komentarze. W rezultacie zmieniłem sposób liczenia wierszy: upuszczam puste i czyste komentarze. Niniejszym gwarantuję wszystkie linie, ale import ma mniej niż 80 znaków. Nie upuszczam podpisów typów, ponieważ ten, który zostawiłem, jest naprawdę potrzebny, aby poprawnie się skompilować (dziękuję bardzo za czystość ST).

Jak mówią komentarze do kodu, 5 wierszy (import Data.Char, 8-bitowe odwzorowania rejestrów i zrzut ekranu) są niezgodne ze specyfikacją, więc możesz je zdyskontować, jeśli masz na to ochotę :-)


3
Niezłe. Jest naprawdę krótki, szczególnie w porównaniu do mojego rozwiązania i drugiego. Twój kod też wygląda bardzo dobrze, chociaż najpierw muszę się nauczyć Haskella.
skopiuj

3
Dobra robota! Bardzo krótki. Powinienem nauczyć się haskell.
ja

Co jest .|.? / 10char
Soham Chowdhury

@octatoan operacja znana w x86 opcodes jako OR.
JB

46

C - 7143 linii (sam procesor 3162 linii)

EDYCJA: Kompilacja systemu Windows ma teraz menu rozwijane umożliwiające zmianę dysków wirtualnych.

Napisałem pełny emulator PC 80186 / V20 (z CGA / MCGA / VGA, sound blaster, adlib, myszą itp.), Emulowanie 8086 w żaden sposób nie jest trywialne. Osiągnięcie pełnej dokładności zajęło wiele miesięcy. Oto moduł CPU tylko z mojego emulatora.

http://sourceforge.net/p/fake86/code/ci/master/tree/src/fake86/cpu.c

Jako pierwszy przyznam, że w tym emulatorze używam zbyt wielu zmiennych globalnych. Zacząłem pisać to, kiedy byłem całkiem nowy w C, i to pokazuje. Pewnego dnia muszę to trochę posprzątać. Większość innych plików źródłowych w nim nie wygląda tak brzydko.

Cały kod (i kilka zrzutów ekranu, jeden poniżej) można zobaczyć tutaj: http://sourceforge.net/p/fake86

Byłbym bardzo szczęśliwy, mogąc pomóc każdemu, kto chce napisać własny, ponieważ jest to świetna zabawa, a ty dużo wiesz o procesorze! Oświadczenie: Nie dodałem emulacji 8080 V20, ponieważ prawie nigdy nie była używana w programie na PC. Wydaje się, że to dużo pracy bez zysku.

Street Fighter 2!


3
Dobra robota! Czy gry faktycznie działają na pełnych obrotach?
skopiuj

1
Dzięki. Tak, działa wiele razy szybciej niż 8088. W nowoczesnym systemie może osiągnąć prędkości podobne do 486. Na naprawdę dobrym procesorze jest to jak niskiej klasy Pentium. Niestety emulacji procesora nie można tak naprawdę wielowątkowo. Robię wszystkie renderowania wideo we własnym wątku. Uruchomiłem go również na moim starym 400 MHz PowePC G3, ponieważ jest to zgodne z prawdziwymi prędkościami 8088.
Mike C

1
Niesamowite! Chciałem również wprowadzić więcej kodów operacyjnych i segmentacji; jednak nie udało się znaleźć bardzo wielu programów testowych do uruchomienia na nim. Czy pobrałeś stare romy?
Dave C

1
Dave, nie, w rzeczywistości zaskakująco poważnie brakuje 8086 ROMów testowych, jak się dowiedziałeś. Sposób, w jaki to zrobiłem, to po prostu zacząć od poprawnego działania ogólnej pamięci ROM BIOS XT. Jeśli tyle działa, Twoja segmentacja jest prawdopodobnie w porządku. Potem było tylko debugowanie, dopóki DOS nie zaczął działać ... a potem aplikacji i gier! :)
Mike C

1
@MikeC Chciałbym trochę pomocy dla początkujących lub wskazówek! (Pun Intended: P). Od wielu lat jestem programistą aplikacji na komputery i aplikacje internetowe i powoli doszedłem do tego, że mam kod źródłowy linuksa. Ogólnie rozumiem, jak działają różne elementy systemu operacyjnego i mogłem grać z małymi zabawkowymi projektami OS. Ale interakcja z bezpośrednim sprzętem po prostu mi umyka!
gideon

41

Postscriptum (130 200 367 517 531 222 246 linii)

Wciąż w toku, aleChciałem pokazać kod, aby zachęcić innych do pokazania kodu .

Zestaw rejestrów jest reprezentowany jako jeden ciąg, więc różne rejestry wielkości bajtów i słów mogą naturalnie nakładać się na siebie, odnosząc się do podłańcuchów. Podciągi są używane jako wskaźniki w całym tekście, dzięki czemu rejestr i lokalizacja pamięci (podciąg ciągu pamięci) mogą być traktowane jednolicie w funkcjach operatora.

Następnie jest garść słów do pobierania i przechowywania danych (bajtu lub słowa) z „wskaźnika”, z pamięci, z pamięci [(IP)] (zwiększanie adresu IP). Następnie istnieje kilka funkcji pobierania bajtu MOD-REG-R / M oraz ustawiania zmiennych REG i R / M i MOD oraz dekodowania ich za pomocą tabel. Następnie operator wprowadza klucz do bajtu kodu operacji. Zatem pętla wykonania jest po prostu fetchb load exec.

Zaimplementowałem tylko garść kodów, ale gOtrzymanie dekodowania operandu wydawało się takim kamieniem milowym, że chciałem się nim podzielić.

edycja: Dodano słowa do rozszerzenia liczb ujemnych. Więcej kodów. Poprawka w przypisaniach rejestru. Komentarze Nadal pracuję nad flagami i wypełnianiem operatorów. Dane wyjściowe dają pewne możliwości: wyjściowy tekst na standardowe wyjście po zakończeniu, ciągłe wyświetlanie przy użyciu kodów vt100, wyświetlanie w oknie obrazu przy użyciu czcionki CP437.

edycja: Zakończono pisanie, rozpoczęto debugowanie. Otrzymuje pierwsze cztery kropki wyniku! Wtedy przeniesienie idzie źle. Senny.

edycja: Wydaje mi się, że posortowałem flagę Carry. Część historii wydarzyła się na comp.lang.postscript . Dodałem urządzenie do debugowania, a dane wyjściowe trafiają do okna graficznego (używając mojej wcześniej napisanej czcionki Code-Page 437 Type-3 ), więc tekst może być pełen śladów i zrzutów. Pisze „Hello World!” a potem jest ta podejrzana karetka. A potem całe mnóstwo nic. :( Dotrzemy tam. Dziękujemy za całą zachętę!

edycja: uruchamia test do końca. Ostatnie kilka błędów to: XCHG robi 2 {read store}, które oczywiście kopiuje, a nie wymienia, ORAZ nie ustawia flag, (FE) INC próbuje uzyskać słowo ze wskaźnika bajtowego.

edycja: Całkowite ponowne pisanie od zera za pomocą zwięzłej tabeli z podręcznika ( zmieniłem nową stronę! ). Zaczynam myśleć, że pominięcie sklepu w opcodes było złym pomysłem, ale pomogło utrzymać optab ładnym. Tym razem brak zrzutu ekranu. Dodałem licznik instrukcji i mod-trigger, aby zrzucić pamięć wideo, dzięki czemu łatwo przeplata się z informacjami o debugowaniu.

edit: Ponownie uruchamia program testowy! Ostatnie kilka błędów krótszego przepisywania było zaniedbywaniem znakowania bezpośredniego bajtu w opcodes 83 (grupa „Natychmiastowa”) i EB (krótki JMP). 24-liniowy wzrost obejmuje dodatkowe procedury debugowania potrzebne do wyśledzenia tych ostatnich błędów.

%!
%a8086.ps Draught2:BREVITY
[/NULL<0000>/nul 0
/mem 16#ffff string %16-bit memory
/CF 0 /OF 0 /AF 0 /ZF 0 /SF 0
/regs 20 string >>begin %register byte storage
0{AL AH CL CH DL DH BL BH}{regs 2 index 1 getinterval def 1 add}forall pop
0{AX CX DX BX SP BP SI DI IP FL}{regs 2 index 2 getinterval def 2 add}forall pop

%getting and fetching
[/*b{0 get} %get byte from pointer
/*w{dup *b exch 1 get bbw} %get word from pointer
/*{{*b *w}W get exec} %get data(W) from pointer
/bbw{8 bitshift add} %lo-byte hi-byte -> word
/shiftmask{2 copy neg bitshift 3 1 roll 1 exch bitshift 1 sub and}
/fetchb{IP *w mem exch get bytedump   IP dup *w 1 add storew} % byte(IP++)
/fetchw{fetchb fetchb bbw} % word(IP),IP+=2

%storing and accessing
/storeb{16#ff and 0 exch put} % ptr val8 -> -
/storew{2 copy storeb -8 bitshift 16#ff and 1 exch put} % ptr val16 -> -
/stor{{storeb storew}W get exec} % ptr val(W) -> -
/memptr{16#ffff and mem exch {1 2}W get getinterval} % addr -> ptr(W)

%decoding the mod-reg-reg/mem byte
/mrm{fetchb 3 shiftmask /RM exch def 3 shiftmask /REG exch def /MOD exch def}
/REGTAB[[AL CL DL BL AH CH DH BH][AX CX DX BX SP BP SI DI]]
/decreg{REGTAB W get REG get} % REGTAB[W][REG]
%2 indexes,   with immed byte,   with immed word
/2*w{exch *w exch *w add}/fba{fetchb add}/fwa{fetchw add}
/RMTAB[[{BX SI 2*w}{BX DI 2*w}{BP SI 2*w}{BP DI 2*w}
    {SI *w}{DI *w}{fetchw}{BX *w}]
[{BX SI 2*w fba}{BX DI 2*w fba}{BP SI 2*w fba}{BP DI 2*w fba}
    {SI *w fba}{DI *w fba}{BP *w fba}{BX *w fba}]
[{BX SI 2*w fwa}{BX DI 2*w fwa}{BP SI 2*w fwa}{BP DI 2*w fwa}
    {SI *w fwa}{DI *w fwa}{BP *w fwa}{BX *w fwa}]]
/decrm{MOD 3 eq{REGTAB W get RM get} %MOD=3:register mode
    {RMTAB MOD get RM get exec memptr}ifelse} % RMTAB[MOD][RM] -> addr -> ptr

%setting and storing flags
/flagw{OF 11 bitshift SF 7 bitshift or ZF 6 bitshift or AF 4 bitshift CF or}
/wflag{dup 1 and /CF exch def dup -4 bitshift 1 and /AF exch def
    dup -6 bitshift 1 and /ZF exch def dup -7 bitshift 1 and /SF exch def
    dup -11 bitshift 1 and /OF exch def}
/nz1{0 ne{1}{0}ifelse}
/logflags{/CF 0 def /OF 0 def /AF 0 def %clear mathflags
    dup {16#80 16#8000}W get and nz1 /SF exch def
    dup {16#ff 16#ffff}W get and 0 eq{1}{0}ifelse /ZF exch def}
/mathflags{{z y x}{exch def}forall
    /CF z {16#ff00 16#ffff0000}W get and nz1 def
    /OF z x xor z y xor and {16#80 16#8000}W get and nz1 def
    /AF x y xor z xor 16#10 and nz1 def
    z} %leave the result on stack

%opcodes (each followed by 'stor')  %% { OPTAB fetchb get exec stor } loop
/ADD{2 copy add logflags mathflags}
/OR{or logflags}
/ADC{CF add ADD}
/SBB{D 1 xor {exch}repeat CF add 2 copy sub logflags mathflags}
/AND{and logflags}
/SUB{D 1 xor {exch}repeat 2 copy sub logflags mathflags}
/XOR{xor logflags}
/CMP{3 2 roll pop NULL 3 1 roll SUB} %dummy stor target
/INC{t CF exch dup * 1 ADD 3 2 roll /CF exch def}
/DEC{t CF exch dup * 1 SUB 3 2 roll /CF exch def}
/PUSH{SP dup *w 2 sub storew   *w SP *w memptr exch}
/POP{SP *w memptr *w   SP dup *w 2 add storew}

/jrel{w {CBW IP *w add IP exch}{NULL exch}ifelse}
/JO{fetchb OF 1 eq jrel }
/JNO{fetchb OF 0 eq jrel }
/JB{fetchb CF 1 eq jrel }
/JNB{fetchb CF 0 eq jrel }
/JZ{fetchb ZF 1 eq jrel }
/JNZ{fetchb ZF 0 eq jrel }
/JBE{fetchb CF ZF or 1 eq jrel }
/JNBE{fetchb CF ZF or 0 eq jrel }
/JS{fetchb SF 1 eq jrel }
/JNS{fetchb SF 0 eq jrel }
/JL{fetchb SF OF xor 1 eq jrel }
/JNL{fetchb SF OF xor 0 eq jrel }
/JLE{fetchb SF OF xor ZF or 1 eq jrel }
/JNLE{fetchb SF OF xor ZF or 0 eq jrel }

/bw{dup 16#80 and 0 ne{16#ff xor 1 add 16#ffff xor 1 add}if}
/IMMTAB{ADD OR ADC SBB AND SUB XOR CMP }cvlit
/immed{ W 2 eq{ /W 1 def
            mrm decrm dup * fetchb bw
    }{ mrm decrm dup * {fetchb fetchw}W get exec }ifelse
    exch IMMTAB REG get dup == exec }

%/TEST{ }
/XCHG{3 2 roll pop 2 copy exch * 4 2 roll * stor }
/AXCH{w dup AX XCHG }
/NOP{ NULL nul }
/pMOV{D{exch}repeat pop }
/mMOV{ 3 1 roll pop pop }
/MOV{ }
/LEA{w mrm decreg RMTAB MOD get RM get exec }

/CBW{dup 16#80 and 0 ne {16#ff xor 1 add 16#ffff xor 1 add } if }
/CWD{dup 16#8000 and 0 ne {16#ffff xor 1 add neg } if }
/CALL{w xp /xp{}def fetchw IP PUSH storew IP dup *w 3 2 roll add dsp /dsp{}def }
%/WAIT{ }
/PUSHF{NULL dup flagw storew 2 copy PUSH }
/POPF{NULL dup POP *w wflag }
%/SAHF{ }
%/LAHF{ }

%/MOVS{ }
%/CMPS{ }
%/STOS{ }
%/LODS{ }
%/SCAS{ }
/RET{w IP POP storew SP dup * 3 2 roll add }
%/LES{ }
%/LDS{ }

/JMP{IP dup fetchw exch *w add}
/sJMP{IP dup fetchb bw exch *w add}

/HLT{exit}
/CMC{/CF CF 1 xor def NULL nul}
/CLC{/CF 0 def NULL nul}
/STC{/CF 1 def NULL nul}

/NOT{not logflags }
/NEG{neg logflags }
/GRP1TAB{TEST --- NOT NEG MUL IMUL DIV IDIV } cvlit
/Grp1{mrm decrm dup * GRP1TAB REG get
dup ==
exec }
/GRP2TAB{INC DEC {id CALL}{l id CALL}{id JMP}{l id JMP} PUSH --- } cvlit
/Grp2{mrm decrm GRP2TAB REG get
dup ==
exec }

%optab shortcuts
/2*{exch * exch *}
/rm{mrm decreg decrm D index 3 1 roll 2*} % fetch,decode mrm -> dest *reg *r-m
/rmp{mrm decreg decrm D index 3 1 roll} % fetch,decode mrm -> dest reg r-m
/ia{ {{AL dup *b fetchb}{AX dup *w fetchw}}W get exec } %immed to accumulator
/is{/W 2 def}
/b{/W 0 def} %select byte operation
/w{/W 1 def} %select word operation
/t{/D 1 def} %dest = reg
/f{/D 0 def} %dest = r/m
/xp{} /dsp{}
%/far{ /xp { <0000> PUSH storew } /dsp { fetchw pop } def }
/i{ {fetchb fetchw}W get exec }

/OPTAB{
{b f rm ADD}{w f rm ADD}{b t rm ADD}{w t rm ADD}{b ia ADD}{w ia ADD}{ES PUSH}{ES POP} %00-07
 {b f rm OR}{w f rm OR}{b t rm OR}{w t rm OR}{b ia OR}{w ia OR}{CS PUSH}{}            %08-0F
{b f rm ADC}{w f rm ADC}{b t rm ADC}{w t rm ADC}{b ia ADC}{w ia ADC}{SS PUSH}{SS POP} %10-17
 {b f rm SBB}{w f rm SBB}{b t rm SBB}{w t rm SBB}{b ia SBB}{w ia SBB}{DS PUSH}{DS POP}%18-1F
{b f rm AND}{w f rm AND}{b t rm AND}{w t rm AND}{b ia AND}{w ia AND}{ES SEG}{DAA}     %20-27
 {b f rm SUB}{w f rm SUB}{b t rm SUB}{w t rm SUB}{b ia SUB}{w ia SUB}{CS SEG}{DAS}    %28-2F
{b f rm XOR}{w f rm XOR}{b t rm XOR}{w t rm XOR}{b ia XOR}{w ia XOR}{SS SEG}{AAA}     %30-37
 {b f rm CMP}{w f rm CMP}{b t rm CMP}{w t rm CMP}{b ia CMP}{w ia CMP}{DS SEG}{AAS}    %38-3F
{w AX INC}{w CX INC}{w DX INC}{w BX INC}{w SP INC}{w BP INC}{w SI INC}{w DI INC}      %40-47
 {w AX DEC}{w CX DEC}{w DX DEC}{w BX DEC}{w SP DEC}{w BP DEC}{w SI DEC}{w DI DEC}     %48-4F
{AX PUSH}{CX PUSH}{DX PUSH}{BX PUSH}{SP PUSH}{BP PUSH}{SI PUSH}{DI PUSH}              %50-57
 {AX POP}{CX POP}{DX POP}{BX POP}{SP POP}{BP POP}{SI POP}{DI POP}                     %58-5F
{}{}{}{}{}{}{}{}  {}{}{}{}{}{}{}{}                                                    %60-6F
{JO}{JNO}{JB}{JNB}{JZ}{JNZ}{JBE}{JNBE} {JS}{JNS}{JP}{JNP}{JL}{JNL}{JLE}{JNLE}         %70-7F

{b f immed}{w f immed}{b f immed}{is f immed}{b TEST}{w TEST}{b rmp XCHG}{w rmp XCHG}   %80-87
 {b f rm pMOV}{w f rm pMOV}{b t rm pMOV}{w t rm pMOV}                                 %88-8B
   {sr f rm pMOV}{LEA}{sr t rm pMOV}{w mrm decrm POP}                                 %8C-8F
{NOP}{CX AXCH}{DX AXCH}{BX AXCHG}{SP AXCH}{BP AXCH}{SI AXCH}{DI AXCH}             %90-97
 {CBW}{CWD}{far CALL}{WAIT}{PUSHF}{POPF}{SAHF}{LAHF}                                  %98-9F
{b AL m MOV}{w AX m MOV}{b m AL MOV}{b AX m MOV}{MOVS}{MOVS}{CMPS}{CMPS}              %A0-A7
 {b i a TEST}{w i a TEST}{STOS}{STOS}{LODS}{LODS}{SCAS}{SCAS}                         %A8-AF
{b AL i MOV}{b CL i MOV}{b DL i MOV}{b BL i MOV}                                      %B0-B3
 {b AH i MOV}{b CH i MOV}{b DH i MOV}{b BH i MOV}                                     %B4-B7
 {w AX i MOV}{w CX i MOV}{w DX i MOV}{w BX i MOV}                                     %B8-BB
 {w SP i MOV}{w BP i MOV}{w SI i MOV}{w DI i MOV}                                     %BC-BF
{}{}{fetchw RET}{0 RET}{LES}{LDS}{b f rm i mMOV}{w f rm i mMOV}                       %C0-B7
 {}{}{fetchw RET}{0 RET}{3 INT}{fetchb INT}{INTO}{IRET}                               %C8-CF
{b Shift}{w Shift}{b v Shift}{w v Shift}{AAM}{AAD}{}{XLAT}                            %D0-D7
 {0 ESC}{1 ESC}{2 ESC}{3 ESC}{4 ESC}{5 ESC}{6 ESC}{7 ESC}                             %D8-DF
{LOOPNZ}{LOOPZ}{LOOP}{JCXZ}{b IN}{w IN}{b OUT}{w OUT}                                 %E0-E7
 {CALL}{JMP}{far JMP}{sJMP}{v b IN}{v w IN}{v b OUT}{v w OUT}                         %E8-EF
{LOCK}{}{REP}{z REP}{HLT}{CMC}{b Grp1}{w Grp}                                         %F0-F7
 {CLC}{STC}{CLI}{STI}{CLD}{STD}{b Grp2}{w Grp2}                                       %F8-FF
}cvlit

/break{ /hook /pause load def }
/c{ /hook {} def }
/doprompt{
    (\nbreak>)print
    flush(%lineedit)(r)file
    cvx {exec}stopped pop }
/pause{ doprompt }
/hook{}

/stdout(%stdout)(w)file
/bytedump{ <00> dup 0 3 index put stdout exch writehexstring ( )print }
/regdump{ REGTAB 1 get{ stdout exch writehexstring ( )print }forall
    stdout IP writehexstring ( )print
    {(NC )(CA )}CF get print
    {(NO )(OV )}OF get print
    {(NS )(SN )}SF get print
    {(NZ )(ZR )}ZF get print
    stdout 16#1d3 w memptr writehexstring
    (\n)print
}
/mainloop{{
    %regdump
    OPTAB fetchb get
    dup ==
    exec
    %pstack flush
    %hook
    stor
    /ic ic 1 add def ictime
}loop}

/printvideo{
    0 1 28 {
        80 mul 16#8000 add mem exch 80 getinterval {
            dup 0 eq { pop 32 } if
                    dup 32 lt 1 index 126 gt or { pop 46 } if
            stdout exch write
        } forall (\n)print
    } for
    (\n)print
}
/ic 0
/ictime{ic 10 mod 0 eq {onq} if}
/timeq 10
/onq{ %printvideo
}
>>begin
currentdict{dup type/arraytype eq 1 index xcheck and
    {bind def}{pop pop}ifelse}forall

SP 16#100 storew
(codegolf.8086)(r)file mem readstring pop
pop[

mainloop
printvideo

%eof

I wyjście (z końcem skróconego wyjścia debugowania).

75 {JNZ}
19 43 {w BX INC}
83 {is f immed}
fb 64 CMP
76 {JBE}
da f4 {HLT}
.........
Hello, world!
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~


################################################################################
##                                                                            ##
##  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987                          ##
##                                                                            ##
##  0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400     ##
##                                                                            ##
##  2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97    ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
################################################################################





GS<1>

5
Zastanawiam się ... Czy klawisz skrótu do zamykania aplikacji alt-F4, ponieważ F4h to kod operacyjny 8086 HLT?
luser droog

5
Chcę tylko powiedzieć, że jesteś niesamowity, jeśli chodzi o implementację tego w Postscript.
cemper93

1
Ten kod jest krótki . Zasługuje na więcej pochwał. Na początek mam mój.
JB

2
czekaj ... PostScript jest językiem programowania ?! ;)
n611x007

32

JavaScript

Piszę emulator 486 w javascript inspirowany przez jslinux. Gdybym wiedział, ile to będzie pracy, prawdopodobnie nigdy bym się nie rozpoczął, ale teraz chcę to zakończyć.

Potem spotkałem się z twoim wyzwaniem i byłem bardzo szczęśliwy, że mogę przetestować program 8086.

http://i.stack.imgur.com/54a6S.png

Możesz „zobaczyć”, jak działa na żywo tutaj: http://codinguncut.com/jsmachine/

Miałem jeden problem podczas drukowania bufora graficznego. Tam, gdzie powinny być spacje, pamięć zawiera elementy „00”. Czy poprawnie interpretuje się „0x00” jako spację, czy mam błąd w emulatorze?

Twoje zdrowie,

Johannes


Ciekawe, tak naprawdę znam twoje imię z twoich Screencastów, które oglądałem po odpowiedzi Haskella w tym wyzwaniu (i uruchomiłem także emulator x86 w JavaScript). Tak, zero bajtów powinno pojawić się jako spacje. Dodałem również zrzut ekranu do twojego postu. W każdym razie +1 :-)
skopiuj

@Johannes Przejrzałem szybko kod mycpu-min.js. Z tego, co mogę powiedzieć, wykorzystałeś tylko kilka pomysłów z cpux86.js (z jslinux FB). Gratulacje! Dobra robota. Czy są jakieś szanse, aby gdzieś zobaczyć nieskompilowane mycpu.js? Mam nadzieję, że na github.com/codinguncut
Yauhen Yakimovich,

@YauhenYakimovich Nie, nie użyłem ponownie żadnego kodu jslinux. Do tej pory zaimplementowałem wszystkie 286 instrukcji minus stronicowanie i segmentacja (mmu). Mój plan polegał na wydaniu kodu na licencji GPL, ale naprawdę chciałbym skomercjalizować pomysł uruchomienia, tj. Freedos lub Reactos, więc nadal nie jestem pewien, czy mam licencję. Prawda jest taka, że ​​dużo czasu zajmie mi wdrożenie pełnej pamięci mgmt. a potem dużo czasu, aby przyspieszyć. Na pewno podzielę się na github.com/codinguncut. Dziękujemy za opinię, Johannes
fluquid

1
Link jest dla mnie zepsuty. (IE na Windows 8)
Conor O'Brien

To przychodzi bardzo późno. Znak zero to kolejna przestrzeń w pamięci RAM wideo.
Joshua

30

C ++

Chciałbym przesłać nasz wpis dotyczący tego wyzwania dotyczącego kodu. Został napisany w c ++ i doskonale uruchamia program testowy. Wdrożyliśmy 90% jednobajtowych kodów operacyjnych i podstawowej segmentacji (niektóre wyłączone, ponieważ nie działa z programem testowym).

Zapis programu: http://davecarruth.com/index.php/2012/04/15/creating-an-8086-emulator

Możesz znaleźć kod w pliku zip na końcu posta na blogu.

Zrzut ekranu wykonujący program testowy: wprowadź opis zdjęcia tutaj

Zajęło to trochę czasu ... jeśli masz jakieś pytania lub komentarze, napisz do mnie. Było to z pewnością świetne ćwiczenie w programowaniu partnerów.


3
Zawsze dobrze jest, gdy ludzie bawią się tym wyzwaniem :) Kilka uwag: Mój program testowy powinien działać (i był testowany) z wyzerowanymi wszystkimi segmentami. Patrząc na część twojego kodu, zauważyłem, że ret imminstrukcja jest niepoprawna (patrz tutaj ) i brakuje Ci 0xffgrupy. Lubię jednak twoje komunikaty o błędach: rzut „Natychmiastowa wartość nie może zapisać wartości, opóźnić.”;
skopiuj

Mieliśmy dwa główne problemy z programem testowym: 1) Segmentacja - kiedy pojawiło się CALL, pchaliśmy CS na stos ... jedna z funkcji w programie testowym nie podobała się. 2) Program testowy oczekiwał, że nasza pamięć zostanie zainicjowana do zera. W każdym razie dobrze się bawiliśmy, dziękuję bardzo za publikowanie!
Dave C

Być może popełniłeś tam błąd: bliskie skoki ( 0xE8 ) nie przesuwaj csrejestru
skopiuj

To byłby problem, dobry haczyk! Wyglądasz na bardzo doświadczonego z 8086, programowałeś go?
Dave C

1
Właściwie sam pracuję nad projektem emulatora x86. Działa całkiem dobrze z freedami i obecnie pracuję na pełnym 32-bitowym wsparciu; po prostu nie opublikowałem tutaj, ponieważ może to być niesprawiedliwe w stosunku do innych plakatów (a kod źródłowy jest nieco zawalony).
skopiuj

28

do

Wielkie wyzwanie i moje pierwsze. Utworzyłem konto tylko dlatego, że wyzwanie bardzo mnie zaintrygowało. Wadą jest to, że nie mogłem przestać myśleć o wyzwaniu, kiedy miałem prawdziwą, płatną pracę programistyczną.

Czuję się zmuszony do uruchomienia ukończonej emulacji 8086, ale to kolejne wyzwanie ;-)

Kod jest napisany w ANSI-C, więc po prostu skompiluj / połącz pliki .c razem, przekaż plik binarny codegolf i idź.

źródło skompresowane

wprowadź opis zdjęcia tutaj


Dobra robota RichTX!
Dave C

Dzięki Dave. Ty też. Kiedy zaczynałem, nie rozumiałem oczekiwań, że kod będzie tak mały, jak to możliwe, ale wciąż było to wyzwanie.
RichTX,

+1 Zerknąłem na twój kod, aby dowiedzieć się, jak działa flaga przenoszenia.
luser droog

Link jest dla mnie niedostępny.
Tyilo,

25

C ++ 1064 wierszy

Fantastyczny projekt. Zrobiłem emulator Intellivision wiele lat temu, więc wspaniale było znów napiąć mięśnie.

Po około tygodniu pracy nie mogłem być bardziej podekscytowany, gdy to się stało:

.........
╤╤╤╤╤╤╤╤╤╤╤╤╤╤
0123456789:;? @ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\] ^ _ `abcdefghijklmnopqrstuvwxyz {|} ~


######################################################################## ##############################
######################################################################## ######################
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

    0 1 4 9 ♠ a ♣ b ♠ cd ♦ f ☺h `§☺b, ♦ d E f` ♠ i ↓ ♣ b 8 ♠ e Y h ↑ ♦ b = ☺f `

    2 3 4 5 6 7 8 9 a ☺a ☻a ♥ a ♦ a ♣ a ♠ a aa ab ☺b ☻b ♥ b ♦ b ♣ b ♠ b bb b
 c ☺c ☻c ♥ c ♦ c ♣ c ♠ c cc cd ☺d ☻d ♥ d ♦ d ♣ d ♠ d dd de ☺e ☻e ♥ e ♦ e ♣ e ♠ e
 ee ef ☺f ☻f ♥ f ♦ f ♣ f ♠ f ff fg ☺g ☻g ♥ g ♦ g ♣ g ♠ gggh ☺h ☻h ♥
h ♦ h ♣ h ♠ h hh hi ☺i ☻i ♥ i ♦ i ♣ i ♠ i ii i `

Trochę debugowania później i ... SHAZAM! wprowadź opis zdjęcia tutaj

Ponadto przebudowałem oryginalny program testowy bez rozszerzeń 80386, ponieważ chciałem zbudować emulator zgodny z 8086 i nie robić żadnych dodatkowych instrukcji. Bezpośredni link do kodu tutaj: plik zip .

Ok, mam za dużo zabawy. Przerwałem zarządzanie pamięcią i ekranem, a teraz ekran aktualizuje się, gdy bufor ekranu jest zapisywany. Zrobiłem film :)

http://www.youtube.com/watch?v=qnAssaTpmnA

Aktualizacje: Rozpoczęło się pierwsze przejście do segmentacji. W rzeczywistości wdrożono bardzo niewiele instrukcji, ale przetestowałem to, przenosząc CS / DS i SS, i wszystko nadal działa dobrze.

Dodano także podstawową obsługę przerwań. Bardzo szczątkowy. Ale wdrożyłem int 21h, aby wydrukować ciąg. Dodano kilka wierszy do źródła testowego i również je przesłałem.

start:
    sti
    mov ah, 9
    mov dx, joetext
    int 21h
...

joetext:
    db 'This was printed by int 21h$', 0

wprowadź opis zdjęcia tutaj

Jeśli ktoś ma jakiś dość prosty kod asemblujący, który przetestowałby segmenty, chciałbym się nim bawić.

Próbuję dowiedzieć się, jak daleko chcę to zrobić. Pełna emulacja procesora? Tryb VGA? Teraz piszę DOSBox.

12/6: Sprawdź, tryb VGA!

wprowadź opis zdjęcia tutaj


Czy jest szansa na opublikowanie kodu w bezpłatnej witrynie, która nie wymaga rejestracji? Dzięki
Dave C

Nie wiedziałem, że to wymaga rejestracji. Przepraszam za to! Spróbuję to zrobić, kiedy wieczorem wrócę do domu.
JoeFish,

@DaveC, sprawdź najnowszą edycję.
JoeFish,

Zastanawiam się, czy jest tam port wielbłądów. To sprawdziłoby segmenty.
luser droog

To cudownie! Ponownie +1. btw, jest 8086 port wielbłąda bradrodriguez.com/papers/index.html .
luser droog

25

C ++ - 4455 linii

I nie, nie tylko spełniłem wymagania pytania. Zrobiłem CAŁĄ 8086, w tym 16 nigdy wcześniej nieznanych kodów. reenigne pomógł odkryć te kody.

https://github.com/Alegend45/IBM5150


gdzie jest plik 4455 linii? och, znalazłem to. #include "cpu.h"trudno jest zobaczyć.
luser droog

2
(w) instrukcja świętej zmiany!
luser droog

Tak, też będzie gorzej, ponieważ zamierzam również włączyć obsługę NEC V20.
Darius Goad

Przejrzałem blogu reenigne użytkownika . Nie mogę znaleźć niczego na temat tych dodatkowych kodów. Czy to gdzieś online?
luser droog

1
Od dłuższego czasu nie aktualizuje swojego bloga. Jest na # ibm5150 na EFNET, więc możesz go tam poprosić.
Darius Goad

20

JavaScript - 4,404 linii

Natknąłem się na ten post, szukając informacji na temat mojego emulatora. Ten post Codegolf był dla mnie absolutnie nieoceniony. Przykładowy program i związany z nim zespół umożliwiły łatwe debugowanie i sprawdzenie, co się dzieje.

Dziękuję Ci!!!

A oto pierwsza wersja mojego emulatora Javascript 8086.

Ukończony bieg

Cechy:

  • Wszystkie wymagane kody do tego wyzwania oraz niektóre dodatki, które były na tyle podobne, że można je było łatwo kodować
  • Częściowo funkcjonalny tryb tekstowy (80x25) wideo (jeszcze żadnych przerw)
  • Działający stos
  • Podstawowa pamięć (niesegmentowana)
  • Całkiem przyzwoite debugowanie (muszę to mieć)
  • Zestaw czcionek Strona 437 ładuje się dynamicznie z reprezentacji bitmapy

Próbny

Mam wersję demo online, nie krępuj się z nią zagrać i daj mi znać, jeśli znajdziesz błędy :)

http://js86emu.chadrempp.com/

Aby uruchomić program codegolf

1) kliknij przycisk ustawień

wprowadź opis zdjęcia tutaj

2), a następnie po prostu kliknij wczytaj (możesz grać tutaj z opcjami debugowania, takimi jak przechodzenie przez program). Program codegolf jest obecnie jedynym dostępnym, pracuję nad tym, aby uzyskać więcej online.

wprowadź opis zdjęcia tutaj

Źródło

Pełne źródło tutaj. https://github.com/crempp/js86emu

Próbowałem wkleić tutaj treść emulacji 8086 (jak sugeruje klamka), ale przekroczyła limit znaków („Treść jest ograniczona do 30000 znaków; wpisałeś 158 272”).

Oto szybki link do kodu, który tu wkleiłem - https://github.com/crempp/js86emu/blob/39dbcb7106a0aaf59e003cd7f722acb4b6923d87/src/js/emu/cpus/8086.js

*Edit - updated for new demo and repo location


Wow, cudowne! Byłoby jednak idealnie, gdyby kod był w samym poście, ponieważ wolimy, aby nasze posty były niezależne.
Klamka

@Doorknob, nie jestem pewien, czy rozumiem. Czy chcesz, żebym zamieścił 4400 linii kodu w tekście?
crempp,

Umm ... Nie zdawałam sobie sprawy, że to tak długo. Czy mieści się w maksymalnym limicie znaków? Jeśli tak, to tak, byłoby świetnie, gdyby Twój post był samodzielny. Dzięki! :-)
Klamka

13

Jawa

Tak długo chciałem stawić czoła temu wyzwaniu i w końcu poświęciłem temu czas. Do tej pory było to wspaniałe doświadczenie i z dumą mogę stwierdzić, że w końcu je ukończyłem.

Wyjście programu testowego

Źródło

Kod źródłowy jest dostępny na GitHub pod adresem NeatMonster / Intel8086 . Próbowałem dokumentować prawie wszystko za pomocą holly 8086 Family User Manual .

Zamierzam zaimplementować wszystkie brakujące kody i funkcje, więc możesz sprawdzić wersję 1.0 dla wersji zawierającej tylko te wymagane do tego wyzwania.

Ogromne podziękowania dla @copy!


13

Common Lisp - 580 loc (442 bez pustych linii i komentarzy)

Wykorzystałem to wyzwanie jako wymówkę do nauki Common Lisp. Oto wynik:

;;; Program settings

(defparameter *disasm* nil "Whether to disassemble")

(defmacro disasm-instr (on-disasm &body body)
  `(if *disasm*
       ,on-disasm
       (progn ,@body)))

;;; State variables

(defparameter *ram* (make-array (* 64 1024) :initial-element 0 :element-type '(unsigned-byte 8)) "Primary segment")
(defparameter *stack* (make-array (* 64 1024) :initial-element 0 :element-type '(unsigned-byte 8)) "Stack segment")
(defparameter *flags* '(:cf 0 :sf 0 :zf 0) "Flags")
(defparameter *registers* '(:ax 0 :bx 0 :cx 0 :dx 0 :bp 0 :sp #x100 :si 0 :di 0) "Registers")
(defparameter *ip* 0 "Instruction pointer")
(defparameter *has-carried* nil "Whether the last wraparound changed the value")
(defparameter *advance* 0 "Bytes to advance IP by after an operation")

;;; Constants

(defconstant +byte-register-to-word+ '(:al (:ax nil) :ah (:ax t) :bl (:bx nil) :bh (:bx t) :cl (:cx nil) :ch (:cx t) :dl (:dx nil) :dh (:dx t)) "Mapping from byte registers to word registers")
(defconstant +bits-to-register+ '(:ax :cx :dx :bx :sp :bp :si :di) "Mapping from index to word register")
(defconstant +bits-to-byte-register+ '(:al :cl :dl :bl :ah :ch :dh :bh) "Mapping from index to byte register")

;;; Constant mappings

(defun bits->word-reg (bits)
  (elt +bits-to-register+ bits))

(defun bits->byte-reg (bits)
  (elt +bits-to-byte-register+ bits))

(defun address-for-r/m (mod-bits r/m-bits)
  (disasm-instr
      (if (and (= mod-bits #b00) (= r/m-bits #b110))
      (list :disp (peek-at-word))
      (case r/m-bits
        (#b000 (list :base :bx :index :si))
        (#b001 (list :base :bx :index :di))
        (#b010 (list :base :bp :index :si))
        (#b011 (list :base :bp :index :di))
        (#b100 (list :index :si))
        (#b101 (list :index :di))
        (#b110 (list :base :bp))
        (#b111 (list :base :bx))))
    (if (and (= mod-bits #b00) (= r/m-bits #b110))
    (peek-at-word)
    (case r/m-bits
      (#b000 (+ (register :bx) (register :si)))
      (#b001 (+ (register :bx) (register :di)))
      (#b010 (+ (register :bp) (register :si)))
      (#b011 (+ (register :bp) (register :di)))
      (#b100 (register :si))
      (#b101 (register :di))
      (#b110 (register :bp))
      (#b111 (register :bx))))))

;;; Convenience functions

(defun reverse-little-endian (low high)
  "Reverse a little-endian number."
  (+ low (ash high 8)))

(defun negative-p (value is-word)
  (or (if is-word (>= value #x8000) (>= value #x80)) (< value 0)))

(defun twos-complement (value is-word)
  (if (negative-p value is-word)
      (- (1+ (logxor value (if is-word #xffff #xff))))
      value))

(defun wrap-carry (value is-word)
  "Wrap around an carried value."
  (let ((carry (if is-word (>= value #x10000) (>= value #x100))))
    (setf *has-carried* carry)
    (if carry
    (if is-word (mod value #x10000) (mod value #x100))
    value)))

;;; setf-able locations

(defun register (reg)
  (disasm-instr reg
    (getf *registers* reg)))

(defun set-reg (reg value)
  (setf (getf *registers* reg) (wrap-carry value t)))

(defsetf register set-reg)

(defun byte-register (reg)
  (disasm-instr reg
    (let* ((register-to-word (getf +byte-register-to-word+ reg)) (word (first register-to-word)))
      (if (second register-to-word)
      (ash (register word) -8)
      (logand (register word) #x00ff)))))

(defun set-byte-reg (reg value)
  (let* ((register-to-word (getf +byte-register-to-word+ reg)) (word (first register-to-word)) (wrapped-value (wrap-carry value nil)))
    (if (second register-to-word)
    (setf (register word) (+ (ash wrapped-value 8) (logand (register word) #x00ff)))
    (setf (register word) (+ wrapped-value (logand (register word) #xff00))))))

(defsetf byte-register set-byte-reg)

(defun flag (name)
  (getf *flags* name))

(defun set-flag (name value)
  (setf (getf *flags* name) value))

(defsetf flag set-flag)

(defun flag-p (name)
  (= (flag name) 1))

(defun set-flag-p (name is-set)
  (setf (flag name) (if is-set 1 0)))

(defsetf flag-p set-flag-p)

(defun byte-in-ram (location segment)
  "Read a byte from a RAM segment."
  (elt segment location))

(defsetf byte-in-ram (location segment) (value)
  "Write a byte to a RAM segment."
  `(setf (elt ,segment ,location) ,value))

(defun word-in-ram (location segment)
  "Read a word from a RAM segment."
  (reverse-little-endian (elt segment location) (elt segment (1+ location))))

(defsetf word-in-ram (location segment) (value)
  "Write a word to a RAM segment."
  `(progn
     (setf (elt ,segment ,location) (logand ,value #x00ff))
     (setf (elt ,segment (1+ ,location)) (ash (logand ,value #xff00) -8))))

(defun indirect-address (mod-bits r/m-bits is-word)
  "Read from an indirect address."
  (disasm-instr
      (if (= mod-bits #b11) (register (if is-word (bits->word-reg r/m-bits) (bits->byte-reg r/m-bits)))
      (let ((base-index (address-for-r/m mod-bits r/m-bits)))
        (unless (getf base-index :disp)
          (setf (getf base-index :disp)
            (case mod-bits
              (#b00 0)
              (#b01 (next-instruction))
              (#b10 (next-word)))))
        base-index))
    (let ((address-base (address-for-r/m mod-bits r/m-bits)))
      (case mod-bits
    (#b00 (if is-word (word-in-ram address-base *ram*) (byte-in-ram address-base *ram*)))
    (#b01 (if is-word (word-in-ram (+ address-base (peek-at-instruction)) *ram*) (byte-in-ram (+ address-base (peek-at-instruction)) *ram*)))
    (#b10 (if is-word (word-in-ram (+ address-base (peek-at-word)) *ram*) (byte-in-ram (+ address-base (peek-at-word)) *ram*)))
    (#b11 (if is-word (register (bits->word-reg r/m-bits)) (byte-register (bits->byte-reg r/m-bits))))))))

(defsetf indirect-address (mod-bits r/m-bits is-word) (value)
  "Write to an indirect address."
  `(let ((address-base (address-for-r/m ,mod-bits ,r/m-bits)))
    (case ,mod-bits
      (#b00 (if ,is-word (setf (word-in-ram address-base *ram*) ,value) (setf (byte-in-ram address-base *ram*) ,value)))
      (#b01 (if ,is-word (setf (word-in-ram (+ address-base (peek-at-instruction)) *ram*) ,value) (setf (byte-in-ram (+ address-base (peek-at-instruction)) *ram*) ,value)))
      (#b10 (if ,is-word (setf (word-in-ram (+ address-base (peek-at-word)) *ram*) ,value) (setf (byte-in-ram (+ address-base (peek-at-word)) *ram*) ,value)))
      (#b11 (if ,is-word (setf (register (bits->word-reg ,r/m-bits)) ,value) (setf (byte-register (bits->byte-reg ,r/m-bits)) ,value))))))

;;; Instruction loader

(defun load-instructions-into-ram (instrs)
  (setf *ip* 0)
  (setf (subseq *ram* 0 #x7fff) instrs)
  (length instrs))

(defun next-instruction ()
  (incf *ip*)
  (elt *ram* (1- *ip*)))

(defun next-word ()
  (reverse-little-endian (next-instruction) (next-instruction)))

(defun peek-at-instruction (&optional (forward 0))
  (incf *advance*)
  (elt *ram* (+ *ip* forward)))

(defun peek-at-word ()
  (reverse-little-endian (peek-at-instruction) (peek-at-instruction 1)))

(defun advance-ip ()
  (incf *ip* *advance*)
  (setf *advance* 0))

(defun advance-ip-ahead-of-indirect-address (mod-bits r/m-bits)
  (cond
    ((or (and (= mod-bits #b00) (= r/m-bits #b110)) (= mod-bits #b10)) 2)
    ((= mod-bits #b01) 1)
    (t 0)))

(defun next-instruction-ahead-of-indirect-address (mod-bits r/m-bits)
  (let ((*ip* *ip*))
    (incf *ip* (advance-ip-ahead-of-indirect-address mod-bits r/m-bits))
    (incf *advance*)
    (next-instruction)))

(defun next-word-ahead-of-indirect-address (mod-bits r/m-bits)
  (let ((*ip* *ip*))
    (incf *ip* (advance-ip-ahead-of-indirect-address mod-bits r/m-bits))
    (incf *advance* 2)
    (next-word)))

;;; Memory access

(defun read-word-from-ram (loc &optional (segment *ram*))
  (word-in-ram loc segment))

(defun write-word-to-ram (loc word &optional (segment *ram*))
  (setf (word-in-ram loc segment) word))

(defun push-to-stack (value)
  (decf (register :sp) 2)
  (write-word-to-ram (register :sp) value *stack*))

(defun pop-from-stack ()
  (incf (register :sp) 2)
  (read-word-from-ram (- (register :sp) 2) *stack*))

;;; Flag effects

(defun set-cf-on-add (value)
  (setf (flag-p :cf) *has-carried*)
  value)

(defun set-cf-on-sub (value1 value2)
  (setf (flag-p :cf) (> value2 value1))
  (- value1 value2))

(defun set-sf-on-op (value is-word)
  (setf (flag-p :sf) (negative-p value is-word))
  value)

(defun set-zf-on-op (value)
  (setf (flag-p :zf) (= value 0))
  value)

;;; Operations

;; Context wrappers

(defun with-one-byte-opcode-register (opcode fn)
  (let ((reg (bits->word-reg (mod opcode #x08))))
    (funcall fn reg)))

(defmacro with-mod-r/m-byte (&body body)
  `(let* ((mod-r/m (next-instruction)) (r/m-bits (logand mod-r/m #b00000111)) (mod-bits (ash (logand mod-r/m #b11000000) -6)) (reg-bits (ash (logand mod-r/m #b00111000) -3)))
     ,@body))

(defmacro with-in-place-mod (dest mod-bits r/m-bits &body body)
  `(progn
     ,@body
     (when (equal (car ',dest) 'indirect-address)
       (decf *advance* (advance-ip-ahead-of-indirect-address ,mod-bits ,r/m-bits)))))

;; Templates

(defmacro mov (src dest)
  `(disasm-instr (list "mov" :src ,src :dest ,dest)
     (setf ,dest ,src)))

(defmacro xchg (op1 op2)
  `(disasm-instr (list "xchg" :op1 ,op1 :op2 ,op2)
     (rotatef ,op1 ,op2)))

(defmacro inc (op1 is-word)
  `(disasm-instr (list "inc" :op1 ,op1)
     (set-sf-on-op (set-zf-on-op (incf ,op1)) ,is-word)))

(defmacro dec (op1 is-word)
  `(disasm-instr (list "dec" :op1 ,op1)
     (set-sf-on-op (set-zf-on-op (decf ,op1)) ,is-word)))

;; Group handling

(defmacro parse-group-byte-pair (opcode operation mod-bits r/m-bits)
  `(,operation ,mod-bits ,r/m-bits (oddp ,opcode)))

(defmacro parse-group-opcode (&body body)
  `(with-mod-r/m-byte
     (case reg-bits
       ,@body)))

;; One-byte opcodes on registers

(defun clear-carry-flag ()
  (disasm-instr '("clc")
    (setf (flag-p :cf) nil)))

(defun set-carry-flag ()
  (disasm-instr '("stc")
    (setf (flag-p :cf) t)))

(defun push-register (reg)
  (disasm-instr (list "push" :src reg)
    (push-to-stack (register reg))))

(defun pop-to-register (reg)
  (disasm-instr (list "pop" :dest reg)
    (setf (register reg) (pop-from-stack))))

(defun inc-register (reg)
  (inc (register reg) t))

(defun dec-register (reg)
  (dec (register reg) t))

(defun xchg-register (reg)
  (disasm-instr (if (eql reg :ax) '("nop") (list "xchg" :op1 :ax :op2 reg))
    (xchg (register :ax) (register reg))))

(defun mov-byte-to-register (opcode)
  (let ((reg (bits->byte-reg (mod opcode #x08))))
    (mov (next-instruction) (byte-register reg))))

(defun mov-word-to-register (reg)
  (mov (next-word) (register reg)))

;; Flow control

(defun jmp-short ()
  (disasm-instr (list "jmp" :op1 (twos-complement (next-instruction) nil))
    (incf *ip* (twos-complement (next-instruction) nil))))

(defmacro jmp-short-conditionally (opcode condition mnemonic)
  `(let ((disp (next-instruction)))
     (if (evenp ,opcode)
       (disasm-instr (list (concatenate 'string "j" ,mnemonic) :op1 (twos-complement disp nil))
     (when ,condition
       (incf *ip* (twos-complement disp nil))))
       (disasm-instr (list (concatenate 'string "jn" ,mnemonic) :op1 (twos-complement disp nil))
     (unless ,condition
       (incf *ip* (twos-complement disp nil)))))))

(defun call-near ()
  (disasm-instr (list "call" :op1 (twos-complement (next-word) t))
    (push-to-stack (+ *ip* 2))
    (incf *ip* (twos-complement (next-word) t))))

(defun ret-from-call ()
  (disasm-instr '("ret")
    (setf *ip* (pop-from-stack))))

;; ALU

(defmacro parse-alu-opcode (opcode operation)
  `(let ((mod-8 (mod ,opcode 8)))
     (case mod-8
       (0
    (with-mod-r/m-byte
      (,operation (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits nil) nil mod-bits r/m-bits)))
       (1
    (with-mod-r/m-byte
      (,operation (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits t) t mod-bits r/m-bits)))
       (2
    (with-mod-r/m-byte
      (,operation (indirect-address mod-bits r/m-bits nil) (byte-register (bits->byte-reg reg-bits)) nil)))
       (3
    (with-mod-r/m-byte
      (,operation (indirect-address mod-bits r/m-bits t) (register (bits->word-reg reg-bits)) t)))
       (4
    (,operation (next-instruction) (byte-register :al) nil))
       (5
    (,operation (next-word) (register :ax) t)))))

(defmacro add-without-carry (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "add" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (set-cf-on-add (incf ,dest ,src)) ,is-word)))))

(defmacro add-with-carry (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "adc" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (set-cf-on-add (incf ,dest (+ ,src (flag :cf)))) ,is-word)))))

(defmacro sub-without-borrow (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "sub" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (let ((src-value ,src))
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub (+ (decf ,dest src-value) src-value) src-value) ,is-word))))))

(defmacro sub-with-borrow (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "sbb" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (let ((src-plus-cf (+ ,src (flag :cf))))
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub (+ (decf ,dest src-plus-cf) src-plus-cf) src-plus-cf) ,is-word))))))

(defmacro cmp-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "cmp" :src ,src :dest ,dest)
     (set-zf-on-op (set-sf-on-op (set-cf-on-sub ,dest ,src) ,is-word))))

(defmacro and-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "and" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logand ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro or-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "or" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logior ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro xor-operation (src dest is-word &optional mod-bits r/m-bits)
  `(disasm-instr (list "xor" :src ,src :dest ,dest)
     (with-in-place-mod ,dest ,mod-bits ,r/m-bits
       (set-zf-on-op (set-sf-on-op (setf ,dest (logxor ,src ,dest)) ,is-word))
       (setf (flag-p :cf) nil))))

(defmacro parse-group1-byte (opcode operation mod-bits r/m-bits)
  `(case (mod ,opcode 4)
    (0 (,operation (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits nil) nil mod-bits r/m-bits))
    (1 (,operation (next-word-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits t) t mod-bits r/m-bits))
    (3 (,operation (twos-complement (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) nil) (indirect-address ,mod-bits ,r/m-bits t) t mod-bits r/m-bits))))

(defmacro parse-group1-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group1-byte ,opcode add-without-carry mod-bits r/m-bits))
     (1 (parse-group1-byte ,opcode or-operation mod-bits r/m-bits))
     (2 (parse-group1-byte ,opcode add-with-carry mod-bits r/m-bits))
     (3 (parse-group1-byte ,opcode sub-with-borrow mod-bits r/m-bits))
     (4 (parse-group1-byte ,opcode and-operation mod-bits r/m-bits))
     (5 (parse-group1-byte ,opcode sub-without-borrow mod-bits r/m-bits))
     (6 (parse-group1-byte ,opcode xor-operation mod-bits r/m-bits))
     (7 (parse-group1-byte ,opcode cmp-operation mod-bits r/m-bits))))

;; Memory and register mov/xchg

(defun xchg-memory-register (opcode)
  (let ((is-word (oddp opcode)))
    (with-mod-r/m-byte
      (if is-word
      (xchg (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits is-word))
      (xchg (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits is-word))))))

(defmacro mov-immediate-to-memory (mod-bits r/m-bits is-word)
  `(if ,is-word
       (mov (next-word-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits t))
       (mov (next-instruction-ahead-of-indirect-address ,mod-bits ,r/m-bits) (indirect-address ,mod-bits ,r/m-bits nil))))

(defmacro parse-group11-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group-byte-pair ,opcode mov-immediate-to-memory mod-bits r/m-bits))))

(defmacro parse-mov-opcode (opcode)
  `(let ((mod-4 (mod ,opcode 4)))
     (with-mod-r/m-byte
       (case mod-4
     (0
      (mov (byte-register (bits->byte-reg reg-bits)) (indirect-address mod-bits r/m-bits nil)))
     (1
      (mov (register (bits->word-reg reg-bits)) (indirect-address mod-bits r/m-bits t)))
     (2
      (mov (indirect-address mod-bits r/m-bits nil) (byte-register (bits->byte-reg reg-bits))))
     (3
      (mov (indirect-address mod-bits r/m-bits t) (register (bits->word-reg reg-bits))))))))

;; Group 4/5 (inc/dec on EAs)

(defmacro inc-indirect (mod-bits r/m-bits is-word)
  `(inc (indirect-address ,mod-bits ,r/m-bits ,is-word) ,is-word))

(defmacro dec-indirect (mod-bits r/m-bits is-word)
  `(dec (indirect-address ,mod-bits ,r/m-bits ,is-word) ,is-word))

(defmacro parse-group4/5-opcode (opcode)
  `(parse-group-opcode
     (0 (parse-group-byte-pair ,opcode inc-indirect mod-bits r/m-bits))
     (1 (parse-group-byte-pair ,opcode dec-indirect mod-bits r/m-bits))))

;;; Opcode parsing

(defun in-paired-byte-block-p (opcode block)
  (= (truncate (/ opcode 2)) (/ block 2)))

(defun in-4-byte-block-p (opcode block)
  (= (truncate (/ opcode 4)) (/ block 4)))

(defun in-8-byte-block-p (opcode block)
  (= (truncate (/ opcode 8)) (/ block 8)))

(defun in-6-byte-block-p (opcode block)
  (and (= (truncate (/ opcode 8)) (/ block 8)) (< (mod opcode 8) 6)))

(defun parse-opcode (opcode)
  "Parse an opcode."
  (cond
    ((not opcode) (return-from parse-opcode nil))
    ((= opcode #xf4) (return-from parse-opcode '("hlt")))
    ((in-8-byte-block-p opcode #x40) (with-one-byte-opcode-register opcode #'inc-register))
    ((in-8-byte-block-p opcode #x48) (with-one-byte-opcode-register opcode #'dec-register))
    ((in-8-byte-block-p opcode #x50) (with-one-byte-opcode-register opcode #'push-register))
    ((in-8-byte-block-p opcode #x58) (with-one-byte-opcode-register opcode #'pop-to-register))
    ((in-8-byte-block-p opcode #x90) (with-one-byte-opcode-register opcode #'xchg-register))
    ((in-8-byte-block-p opcode #xb0) (mov-byte-to-register opcode))
    ((in-8-byte-block-p opcode #xb8) (with-one-byte-opcode-register opcode #'mov-word-to-register))
    ((= opcode #xf8) (clear-carry-flag))
    ((= opcode #xf9) (set-carry-flag))
    ((= opcode #xeb) (jmp-short))
    ((in-paired-byte-block-p opcode #x72) (jmp-short-conditionally opcode (flag-p :cf) "b"))
    ((in-paired-byte-block-p opcode #x74) (jmp-short-conditionally opcode (flag-p :zf) "z"))
    ((in-paired-byte-block-p opcode #x76) (jmp-short-conditionally opcode (or (flag-p :cf) (flag-p :zf)) "be"))
    ((in-paired-byte-block-p opcode #x78) (jmp-short-conditionally opcode (flag-p :sf) "s"))
    ((= opcode #xe8) (call-near))
    ((= opcode #xc3) (ret-from-call))
    ((in-6-byte-block-p opcode #x00) (parse-alu-opcode opcode add-without-carry))
    ((in-6-byte-block-p opcode #x08) (parse-alu-opcode opcode or-operation))
    ((in-6-byte-block-p opcode #x10) (parse-alu-opcode opcode add-with-carry))
    ((in-6-byte-block-p opcode #x18) (parse-alu-opcode opcode sub-with-borrow))
    ((in-6-byte-block-p opcode #x20) (parse-alu-opcode opcode and-operation))
    ((in-6-byte-block-p opcode #x28) (parse-alu-opcode opcode sub-without-borrow))
    ((in-6-byte-block-p opcode #x30) (parse-alu-opcode opcode xor-operation))
    ((in-6-byte-block-p opcode #x38) (parse-alu-opcode opcode cmp-operation))
    ((in-4-byte-block-p opcode #x80) (parse-group1-opcode opcode))
    ((in-4-byte-block-p opcode #x88) (parse-mov-opcode opcode))
    ((in-paired-byte-block-p opcode #x86) (xchg-memory-register opcode))
    ((in-paired-byte-block-p opcode #xc6) (parse-group11-opcode opcode))
    ((in-paired-byte-block-p opcode #xfe) (parse-group4/5-opcode opcode))))

;;; Main functions

(defun execute-instructions ()
  "Loop through loaded instructions."
  (loop
     for ret = (parse-opcode (next-instruction))
     until (equal ret '("hlt"))
     do (advance-ip)
     finally (return t)))

(defun disasm-instructions (instr-length)
  "Disassemble code."
  (loop
     for ret = (parse-opcode (next-instruction))
     collecting ret into disasm
     until (= *ip* instr-length)
     do (advance-ip)
     finally (return disasm)))

(defun loop-instructions (instr-length)
  (if *disasm*
      (disasm-instructions instr-length)
      (execute-instructions)))

(defun load-instructions-from-file (file)
  (with-open-file (in file :element-type '(unsigned-byte 8))
    (let ((instrs (make-array (file-length in) :element-type '(unsigned-byte 8) :initial-element 0 :adjustable t)))
      (read-sequence instrs in)
      instrs)))

(defun load-instructions (&key (file nil))
  (if file
      (load-instructions-from-file file)
      #()))

(defun print-video-ram (&key (width 80) (height 25) (stream t) (newline nil))
  (dotimes (line height)
    (dotimes (column width)
      (let ((char-at-cell (byte-in-ram (+ #x8000 (* line 80) column) *ram*)))
    (if (zerop char-at-cell)
        (format stream "~a" #\Space)
        (format stream "~a" (code-char char-at-cell)))))
    (if newline (format stream "~%"))))

(defun disasm (&key (file nil))
  (setf *disasm* t)
  (loop-instructions (load-instructions-into-ram (load-instructions :file file))))

(defun main (&key (file nil) (display nil) (stream t) (newline nil))
  (setf *disasm* nil)
  (loop-instructions (load-instructions-into-ram (load-instructions :file file)))
  (when display
    (print-video-ram :stream stream :newline newline)))

Oto wynik w Emacsie:

Okno Emacsa z dwoma panelami, sekcją źródła Lisp po lewej stronie i wyjściem REPL z wymaganą zawartością po prawej stronie.

Chcę podkreślić trzy główne cechy. Kod ten znaczący sposób korzysta z makr przy wykonywaniu instrukcji, takich jak mov, xchgoraz artithmetic operacje. Każda instrukcja zawiera disasm-instrwywołanie makra. To implementuje dezasemblację wraz z rzeczywistym kodem przy użyciu zmiennej globalnej if over ustawionej w czasie wykonywania. Jestem szczególnie dumny z podejścia agnostycznego przeznaczonego do zapisywania wartości w rejestrach i adresach pośrednich. Makra implementujące instrukcje nie dbają o miejsce docelowe, ponieważ formularze wstawiane dla każdej z opcji będą działać z ogólnym setfmakrem Common Lisp.

Kod można znaleźć na moim repozytorium GitHub . Poszukaj gałęzi „codegolf”, ponieważ już zacząłem implementować inne funkcje 8086 w master. Zaimplementowałem już flagi przepełnienia i parzystości wraz z rejestrem FLAGS.

Istnieją trzy operacje w tym, nie w 8086, 0x82i 0x83wersjach operatorów logicznych. Zostało to złapane bardzo późno i usunięcie tych operacji byłoby dość niechlujne.

Chciałbym podziękować @ja za jego wersję Pythona, która zainspirowała mnie na początku tego przedsięwzięcia.


3
Niesamowita pierwsza odpowiedź! Witamy na stronie :)
DJMcMayhem

1
Bardzo fajny wybór języka!
skopiuj

12

C - 319 348 linii

To jest mniej więcej bezpośrednie tłumaczenie mojego programu Postscript na C. Oczywiście użycie stosu jest zastąpione wyraźnymi zmiennymi. Pola instrukcji są podzielone na zmienne o- bajt opcode instrukcji, d- pole kierunku, w- pole szerokości. Jeśli jest to instrukcja „mod-reg-r / m”, bajt mr-rm jest wczytywany struct rm r. Dekodowanie pól reg i r / m przebiega w dwóch krokach: obliczenie wskaźnika na dane i załadowanie danych, ponowne użycie tej samej zmiennej. Więc dla czegoś takiego ADD AX,BX, najpierw x jest wskaźnikiem do ax, a y jest wskaźnikiem do bx, następnie x jest zawartością (ax), a y jest treścią (bx). Wymagane jest wiele rzutowania, aby ponownie użyć zmiennej dla różnych typów takich jak ten.

Bajt opkodu jest dekodowany za pomocą tabeli wskaźników funkcji. Każda funkcja składa się z makr dla elementów wielokrotnego użytku. DWMakro jest obecny we wszystkich funkcjach OPCODE i dekoduje di wzmienne z obajtu rozkazu. RMPMakro przeprowadza pierwszy etap dekodowania „MR-RM” bajtów, oraz LDXYprzeprowadza się drugi etap. W kodach, które przechowują wynik, używa się pzmiennej, aby przytrzymać wskaźnik do lokalizacji wyniku, a zzmiennej, aby zachować wartość wyniku. Flagi są obliczane po obliczeniu zwartości. INCI DECoperacje uratować flagę carry przed użyciem rodzajowe MATHFLAGSfunkcję (jako część ADDlubSUB submacro) i przywróć go posłowia, aby zachować Carry.

Edycja: naprawiono błędy!
Edycja: rozwinięty i skomentowany. Kiedy trace==0teraz wysyła komendę ANSI move-to-0,0 podczas zrzutu wideo. Więc lepiej symuluje rzeczywisty obraz. Ta BIGENDIANrzecz (która nawet nie działała) została usunięta. W niektórych miejscach opiera się na kolejności bajtów little-endian, ale planuję to naprawić w następnej wersji. Zasadniczo cały dostęp do wskaźnika musi przejść przez funkcje get_i, put_które jawnie (de) składają bajty w kolejności LE.

#include<ctype.h>
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<unistd.h>
#define P printf
#define R return
#define T typedef
T intptr_t I; T uintptr_t U;
T short S; T unsigned short US;
T signed char C; T unsigned char UC; T void V;  // to make everything shorter
U o,w,d,f; // opcode, width, direction, extra temp variable (was initially for a flag, hence 'f')
U x,y,z;   // left operand, right operand, result
void *p;   // location to receive result
UC halt,debug=0,trace=0,reg[28],null[2],mem[0xffff]={ // operating flags, register memory, RAM
    1, (3<<6),        // ADD ax,ax
    1, (3<<6)+(4<<3), // ADD ax,sp
    3, (3<<6)+(4<<3), // ADD sp,ax
    0xf4 //HLT
};

// register declaration and initialization
#define H(_)_(al)_(ah)_(cl)_(ch)_(dl)_(dh)_(bl)_(bh)
#define X(_)_(ax)     _(cx)     _(dx)     _(bx)     _(sp)_(bp)_(si)_(di)_(ip)_(fl)
#define SS(_)_(cs)_(ds)_(ss)_(es)
#define HD(_)UC*_;      // half-word regs declared as unsigned char *
#define XD(_)US*_;      // full-word regs declared as unsigned short *
#define HR(_)_=(UC*)(reg+i++);      // init and increment by one
#define XR(_)_=(US*)(reg+i);i+=2;   // init and increment by two
H(HD)X(XD)SS(XD)V init(){I i=0;H(HR)i=0;X(XR)SS(XR)}    // declare and initialize register pointers
enum { CF=1<<0, PF=1<<2, AF=1<<4, ZF=1<<6, SF=1<<7, OF=1<<11 };

#define HP(_)P(#_ ":%02x ",*_);     // dump a half-word reg as zero-padded hex
#define XP(_)P(#_ ":%04x ",*_);     // dump a full-word reg as zero-padded hex
V dump(){ //H(HP)P("\n");
    P("\n"); X(XP)
    if(trace)P("%s %s %s %s ",*fl&CF?"CA":"NC",*fl&OF?"OV":"NO",*fl&SF?"SN":"NS",*fl&ZF?"ZR":"NZ");
    P("\n");  // ^^^ crack flag bits into strings ^^^
}

// get and put into memory in a strictly little-endian format
I get_(void*p,U w){R w? *(UC*)p + (((UC*)p)[1]<<8) :*(UC*)p;}
V put_(void*p,U x,U w){ if(w){ *(UC*)p=x; ((UC*)p)[1]=x>>8; }else *(UC*)p=x; }
// get byte or word through ip, incrementing ip
UC fetchb(){ U x = get_(mem+(*ip)++,0); if(trace)P("%02x(%03o) ",x,x); R x; }
US fetchw(){I w=fetchb();R w|(fetchb()<<8);}

T struct rm{U mod,reg,r_m;}rm;      // the three fields of the mod-reg-r/m byte
rm mrm(U m){ R(rm){ (m>>6)&3, (m>>3)&7, m&7 }; }    // crack the mrm byte into fields
U decreg(U reg,U w){    // decode the reg field, yielding a uintptr_t to the register (byte or word)
    if (w)R (U)((US*[]){ax,cx,dx,bx,sp,bp,si,di}[reg]);
    else R (U)((UC*[]){al,cl,dl,bl,ah,ch,dh,bh}[reg]); }
U rs(US*x,US*y){ R get_(x,1)+get_(y,1); }  // fetch and sum two full-words
U decrm(rm r,U w){      // decode the r/m byte, yielding uintptr_t
    U x=(U[]){rs(bx,si),rs(bx,di),rs(bp,si),rs(bp,di),get_(si,1),get_(di,1),get_(bp,1),get_(bx,1)}[r.r_m];
    switch(r.mod){ case 0: if (r.r_m==6) R (U)(mem+fetchw()); break;
                   case 1: x+=fetchb(); break;
                   case 2: x+=fetchw(); break;
                   case 3: R decreg(r.r_m,w); }
    R (U)(mem+x); }

// opcode helpers
    // set d and w from o
#define DW  if(trace){ P("%s:\n",__func__); } \
            d=!!(o&2); \
            w=o&1;
    // fetch mrm byte and decode, setting x and y as pointers to args and p ptr to dest
#define RMP rm r=mrm(fetchb());\
            x=decreg(r.reg,w); \
            y=decrm(r,w); \
            if(trace>1){ P("x:%d\n",x); P("y:%d\n",y); } \
            p=d?(void*)x:(void*)y;

    // fetch x and y values from x and y pointers
#define LDXY \
            x=get_((void*)x,w); \
            y=get_((void*)y,w); \
            if(trace){ P("x:%d\n",x); P("y:%d\n",y); }

    // normal mrm decode and load
#define RM  RMP LDXY

    // immediate to accumulator
#define IA x=(U)(p=w?(UC*)ax:al); \
           x=get_((void*)x,w); \
           y=w?fetchw():fetchb();

    // flags set by logical operators
#define LOGFLAGS  *fl=0; \
                  *fl |= ( (z&(w?0x8000:0x80))           ?SF:0) \
                       | ( (z&(w?0xffff:0xff))==0        ?ZF:0) ;

    // additional flags set by math operators
#define MATHFLAGS *fl |= ( (z&(w?0xffff0000:0xff00))     ?CF:0) \
                       | ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0) \
                       | ( ((x^y^z)&0x10)                ?AF:0) ;

    // store result to p ptr
#define RESULT \
        if(trace)P(w?"->%04x ":"->%02x ",z); \
        put_(p,z,w);

// operators, composed with helpers in the opcode table below
    // most of these macros will "enter" with x and y already loaded with operands
#define PUSH(x) put_(mem+(*sp-=2),*(x),1)
#define POP(x) *(x)=get_(mem+(*sp+=2)-2,1)
#define ADD z=x+y; LOGFLAGS MATHFLAGS RESULT
#define ADC x+=(*fl&CF); ADD
#define SUB z=d?x-y:y-x; LOGFLAGS MATHFLAGS RESULT
#define SBB d?y+=*fl&CF:(x+=*fl&CF); SUB
#define CMP p=null; SUB
#define AND z=x&y; LOGFLAGS RESULT
#define  OR z=x|y; LOGFLAGS RESULT
#define XOR z=x^y; LOGFLAGS RESULT
#define INC(r) w=1; d=1; p=(V*)r; x=(S)*r; y=1; f=*fl&CF; ADD *fl=(*fl&~CF)|f;
#define DEC(r) w=1; d=1; p=(V*)r; x=(S)*r; y=1; f=*fl&CF; SUB *fl=(*fl&~CF)|f;
#define F(f) !!(*fl&f)
#define J(c) U cf=F(CF),of=F(OF),sf=F(SF),zf=F(ZF); y=(S)(C)fetchb(); \
                  if(trace)P("<%d> ", c); \
                  if(c)*ip+=(S)y;
#define JN(c) J(!(c))
#define IMM(a,b) rm r=mrm(fetchb()); \
            p=(void*)(y=decrm(r,w)); \
            a \
            x=w?fetchw():fetchb(); \
            b \
            d=0; \
            y=get_((void*)y,w); \
            if(trace){ P("x:%d\n",x); P("y:%d\n",y); } \
            if(trace){ P("%s ", (C*[]){"ADD","OR","ADC","SBB","AND","SUB","XOR","CMP"}[r.reg]); } \
            switch(r.reg){case 0:ADD break; \
                          case 1:OR break; \
                          case 2:ADC break; \
                          case 3:SBB break; \
                          case 4:AND break; \
                          case 5:SUB break; \
                          case 6:XOR break; \
                          case 7:CMP break; }
#define IMMIS IMM(w=0;,w=1;x=(S)(C)x;)
#define TEST z=x&y; LOGFLAGS MATHFLAGS
#define XCHG f=x;z=y; LDXY if(w){*(US*)f=y;*(US*)z=x;}else{*(UC*)f=y;*(UC*)z=x;}
#define MOV z=d?y:x; RESULT
#define MOVSEG
#define LEA RMP z=((UC*)y)-mem; RESULT
#define NOP
#define AXCH(r) x=(U)ax; y=(U)(r); w=1; XCHG
#define CBW *ax=(S)(C)*al;
#define CWD z=(I)(S)*ax; *dx=z>>16;
#define CALL x=w?fetchw():(S)(C)fetchb(); PUSH(ip); (*ip)+=(S)x;
#define WAIT
#define PUSHF PUSH(fl)
#define POPF POP(fl)
#define SAHF x=*fl; y=*ah; x=(x&~0xff)|y; *fl=x;
#define LAHF *ah=(UC)*fl;
#define mMOV if(d){ x=get_(mem+fetchw(),w); if(w)*ax=x; else*al=x; } \
             else { put_(mem+fetchw(),w?*ax:*al,w); }
#define MOVS
#define CMPS
#define STOS
#define LODS
#define SCAS
#define iMOVb(r) (*r)=fetchb();
#define iMOVw(r) (*r)=fetchw();
#define RET(v) POP(ip); if(v)*sp+=v*2;
#define LES
#define LDS
#define iMOVm if(w){iMOVw((US*)y)}else{iMOVb((UC*)y)}
#define fRET(v) POP(cs); RET(v)
#define INT(v)
#define INT0
#define IRET
#define Shift rm r=mrm(fetchb());
#define AAM
#define AAD
#define XLAT
#define ESC(v)
#define LOOPNZ
#define LOOPZ
#define LOOP
#define JCXZ
#define IN
#define OUT
#define INv
#define OUTv
#define JMP x=fetchw(); *ip+=(S)x;
#define sJMP x=(S)(C)fetchb(); *ip+=(S)x;
#define FARJMP
#define LOCK
#define REP
#define REPZ
#define HLT halt=1
#define CMC *fl=(*fl&~CF)|((*fl&CF)^1);
#define NOT
#define NEG
#define MUL
#define IMUL
#define DIV
#define IDIV
#define Grp1 rm r=mrm(fetchb()); \
             y=decrm(r,w); \
             if(trace)P("%s ", (C*[]){}[r.reg]); \
             switch(r.reg){case 0: TEST; break; \
                           case 2: NOT; break; \
                           case 3: NEG; break; \
                           case 4: MUL; break; \
                           case 5: IMUL; break; \
                           case 6: DIV; break; \
                           case 7: IDIV; break; }
#define Grp2 rm r=mrm(fetchb()); \
             y=decrm(r,w); \
             if(trace)P("%s ", (C*[]){"INC","DEC","CALL","CALL","JMP","JMP","PUSH"}[r.reg]); \
             switch(r.reg){case 0: INC((S*)y); break; \
                           case 1: DEC((S*)y); break; \
                           case 2: CALL; break; \
                           case 3: CALL; break; \
                           case 4: *ip+=(S)y; break; \
                           case 5: JMP; break; \
                           case 6: PUSH((S*)y); break; }
#define CLC *fl=*fl&~CF;
#define STC *fl=*fl|CF;
#define CLI
#define STI
#define CLD
#define STD

// opcode table
// An x-macro table of pairs (a, b) where a becomes the name of a void function(void) which
// implements the opcode, and b comprises the body of the function (via further macro expansion)
#define OP(_)\
/*dw:bf                 wf                     bt                    wt   */ \
_(addbf, RM ADD)      _(addwf, RM ADD)       _(addbt,  RM ADD)     _(addwt, RM ADD)     /*00-03*/\
_(addbi, IA ADD)      _(addwi, IA ADD)       _(pushes, PUSH(es))   _(popes, POP(es))    /*04-07*/\
_(orbf,  RM OR)       _(orwf,  RM OR)        _(orbt,   RM OR)      _(orwt,  RM OR)      /*08-0b*/\
_(orbi,  IA OR)       _(orwi,  IA OR)        _(pushcs, PUSH(cs))   _(nop0,       )      /*0c-0f*/\
_(adcbf, RM ADC)      _(adcwf, RM ADC)       _(adcbt,  RM ADC)     _(adcwt, RM ADC)     /*10-13*/\
_(adcbi, IA ADC)      _(adcwi, IA ADC)       _(pushss, PUSH(ss))   _(popss, POP(ss))    /*14-17*/\
_(sbbbf, RM SBB)      _(sbbwf, RM SBB)       _(sbbbt,  RM SBB)     _(sbbwt, RM SBB)     /*18-1b*/\
_(sbbbi, IA SBB)      _(sbbwi, IA SBB)       _(pushds, PUSH(ds))   _(popds, POP(ds))    /*1c-1f*/\
_(andbf, RM AND)      _(andwf, RM AND)       _(andbt, RM AND)      _(andwt, RM AND)     /*20-23*/\
_(andbi, IA AND)      _(andwi, IA AND)       _(esseg, )            _(daa, )             /*24-27*/\
_(subbf, RM SUB)      _(subwf, RM SUB)       _(subbt, RM SUB)      _(subwt, RM SUB)     /*28-2b*/\
_(subbi, IA SUB)      _(subwi, IA SUB)       _(csseg, )            _(das, )             /*2c-2f*/\
_(xorbf, RM XOR)      _(xorwf, RM XOR)       _(xorbt, RM XOR)      _(xorwt, RM XOR)     /*30-33*/\
_(xorbi, IA XOR)      _(xorwi, IA XOR)       _(ssseg, )            _(aaa, )             /*34-37*/\
_(cmpbf, RM CMP)      _(cmpwf, RM CMP)       _(cmpbt, RM CMP)      _(cmpwt, RM CMP)     /*38-3b*/\
_(cmpbi, IA CMP)      _(cmpwi, IA CMP)       _(dsseg, )            _(aas, )             /*3c-3f*/\
_(incax, INC(ax))     _(inccx, INC(cx))      _(incdx, INC(dx))     _(incbx, INC(bx))    /*40-43*/\
_(incsp, INC(sp))     _(incbp, INC(bp))      _(incsi, INC(si))     _(incdi, INC(di))    /*44-47*/\
_(decax, DEC(ax))     _(deccx, DEC(cx))      _(decdx, DEC(dx))     _(decbx, DEC(bx))    /*48-4b*/\
_(decsp, DEC(sp))     _(decbp, DEC(bp))      _(decsi, DEC(si))     _(decdi, DEC(di))    /*4c-4f*/\
_(pushax, PUSH(ax))   _(pushcx, PUSH(cx))    _(pushdx, PUSH(dx))   _(pushbx, PUSH(bx))  /*50-53*/\
_(pushsp, PUSH(sp))   _(pushbp, PUSH(bp))    _(pushsi, PUSH(si))   _(pushdi, PUSH(di))  /*54-57*/\
_(popax, POP(ax))     _(popcx, POP(cx))      _(popdx, POP(dx))     _(popbx, POP(bx))    /*58-5b*/\
_(popsp, POP(sp))     _(popbp, POP(bp))      _(popsi, POP(si))     _(popdi, POP(di))    /*5c-5f*/\
_(nop1, ) _(nop2, )   _(nop3, ) _(nop4, )    _(nop5, ) _(nop6, )   _(nop7, ) _(nop8, )  /*60-67*/\
_(nop9, ) _(nopA, )   _(nopB, ) _(nopC, )    _(nopD, ) _(nopE, )   _(nopF, ) _(nopG, )  /*68-6f*/\
_(jo, J(of))          _(jno, JN(of))         _(jb, J(cf))          _(jnb, JN(cf))       /*70-73*/\
_(jz, J(zf))          _(jnz, JN(zf))         _(jbe, J(cf|zf))      _(jnbe, JN(cf|zf))   /*74-77*/\
_(js, J(sf))          _(jns, JN(sf))         _(jp, )               _(jnp, )             /*78-7b*/\
_(jl, J(sf^of))       _(jnl_, JN(sf^of))     _(jle, J((sf^of)|zf)) _(jnle,JN((sf^of)|zf))/*7c-7f*/\
_(immb, IMM(,))       _(immw, IMM(,))        _(immb1, IMM(,))      _(immis, IMMIS)      /*80-83*/\
_(testb, RM TEST)     _(testw, RM TEST)      _(xchgb, RMP XCHG)    _(xchgw, RMP XCHG)   /*84-87*/\
_(movbf, RM MOV)      _(movwf, RM MOV)       _(movbt, RM MOV)      _(movwt, RM MOV)     /*88-8b*/\
_(movsegf, RM MOVSEG) _(lea, LEA)            _(movsegt, RM MOVSEG) _(poprm,RM POP((US*)p))/*8c-8f*/\
_(nopH, )             _(xchgac, AXCH(cx))    _(xchgad, AXCH(dx))   _(xchgab, AXCH(bx))  /*90-93*/\
_(xchgasp, AXCH(sp))  _(xchabp, AXCH(bp))    _(xchgasi, AXCH(si))  _(xchadi, AXCH(di))  /*94-97*/\
_(cbw, CBW)           _(cwd, CWD)            _(farcall, )          _(wait, WAIT)        /*98-9b*/\
_(pushf, PUSHF)       _(popf, POPF)          _(sahf, SAHF)         _(lahf, LAHF)        /*9c-9f*/\
_(movalb, mMOV)       _(movaxw, mMOV)        _(movbal, mMOV)       _(movwax, mMOV)      /*a0-a3*/\
_(movsb, MOVS)        _(movsw, MOVS)         _(cmpsb, CMPS)        _(cmpsw, CMPS)       /*a4-a7*/\
_(testaib, IA TEST)   _(testaiw, IA TEST)    _(stosb, STOS)        _(stosw, STOS)       /*a8-ab*/\
_(lodsb, LODS)        _(lodsw, LODS)         _(scasb, SCAS)        _(scasw, SCAS)       /*ac-af*/\
_(movali, iMOVb(al))  _(movcli, iMOVb(cl))   _(movdli, iMOVb(dl))  _(movbli, iMOVb(bl)) /*b0-b3*/\
_(movahi, iMOVb(ah))  _(movchi, iMOVb(ch))   _(movdhi, iMOVb(dh))  _(movbhi, iMOVb(bh)) /*b4-b7*/\
_(movaxi, iMOVw(ax))  _(movcxi, iMOVw(cx))   _(movdxi, iMOVw(dx))  _(movbxi, iMOVw(bx)) /*b8-bb*/\
_(movspi, iMOVw(sp))  _(movbpi, iMOVw(bp))   _(movsii, iMOVw(si))  _(movdii, iMOVw(di)) /*bc-bf*/\
_(nopI, )             _(nopJ, )              _(reti, RET(fetchw())) _(retz, RET(0))     /*c0-c3*/\
_(les, LES)           _(lds, LDS)            _(movimb, RMP iMOVm)  _(movimw, RMP iMOVm) /*c4-c7*/\
_(nopK, )             _(nopL, )              _(freti, fRET(fetchw())) _(fretz, fRET(0)) /*c8-cb*/\
_(int3, INT(3))       _(inti, INT(fetchb())) _(int0, INT(0))       _(iret, IRET)        /*cc-cf*/\
_(shiftb, Shift)      _(shiftw, Shift)       _(shiftbv, Shift)     _(shiftwv, Shift)    /*d0-d3*/\
_(aam, AAM)           _(aad, AAD)            _(nopM, )             _(xlat, XLAT)        /*d4-d7*/\
_(esc0, ESC(0))       _(esc1, ESC(1))        _(esc2, ESC(2))       _(esc3, ESC(3))      /*d8-db*/\
_(esc4, ESC(4))       _(esc5, ESC(5))        _(esc6, ESC(6))       _(esc7, ESC(7))      /*dc-df*/\
_(loopnz, LOOPNZ)     _(loopz, LOOPZ)        _(loop, LOOP)         _(jcxz, JCXZ)        /*e0-e3*/\
_(inb, IN)            _(inw, IN)             _(outb, OUT)          _(outw, OUT)         /*e4-e7*/\
_(call, w=1; CALL)    _(jmp, JMP)            _(farjmp, FARJMP)     _(sjmp, sJMP)        /*e8-eb*/\
_(invb, INv)          _(invw, INv)           _(outvb, OUTv)        _(outvw, OUTv)       /*ec-ef*/\
_(lock, LOCK)         _(nopN, )              _(rep, REP)           _(repz, REPZ)        /*f0-f3*/\
_(hlt, HLT)           _(cmc, CMC)            _(grp1b, Grp1)        _(grp1w, Grp1)       /*f4-f7*/\
_(clc, CLC)           _(stc, STC)            _(cli, CLI)           _(sti, STI)          /*f8-fb*/\
_(cld, CLD)           _(std, STD)            _(grp2b, Grp2)        _(grp2w, Grp2)       /*fc-ff*/
#define OPF(a,b)void a(){DW b;}     // generate opcode function
#define OPN(a,b)a,                  // extract name
OP(OPF)void(*tab[])()={OP(OPN)};    // generate functions, declare and populate fp table with names

V clean(C*s){I i;       // replace unprintable characters in 80-byte buffer with spaces
    for(i=0;i<80;i++)
        if(!isprint(s[i]))
            s[i]=' ';
}
V video(){I i;          // dump the (cleaned) video memory to the console
    C buf[81]="";
    if(!trace)P("\e[0;0;f");
    for(i=0;i<28;i++)
        memcpy(buf, mem+0x8000+i*80, 80),
        clean(buf),
        P("\n%s",buf);
    P("\n");
}

static I ct;        // timer memory for period video dump
V run(){while(!halt){if(trace)dump();
    if(!ct--){ct=10; video();}
    tab[o=fetchb()]();}}
V dbg(){
    while(!halt){
        C c;
        if(!ct--){ct=10; video();}
        if(trace)dump();
        //scanf("%c", &c);
        fgetc(stdin);
        //switch(c){
        //case '\n':
        //case 's':
            tab[o=fetchb()]();
            //break;
        //}
    }
}

I load(C*f){struct stat s; FILE*fp;     // load a file into memory at address zero
    R (fp=fopen(f,"rb"))
        && fstat(fileno(fp),&s) || fread(mem,s.st_size,1,fp); }

I main(I c,C**v){
    init();
    if(c>1){            // if there's an argument
        load(v[1]);     //     load named file
    }
    *sp=0x100;          // initialize stack pointer
    if(debug) dbg();    // if debugging, debug
    else run();         // otherwise, just run
    video();            // dump final video
    R 0;}               // remember what R means? cf. line 9

Używanie makr na etapach różnych operacji zapewnia bardzo ścisłe dopasowanie semantyczne do sposobu działania kodu postscriptowego w czysto sekwencyjny sposób. Na przykład wszystkie cztery pierwsze kody 0x00-0x03 są instrukcjami DODAJ o zmiennym kierunku (REG -> REG / MOD, REG <- REG / MOD) i rozmiarach bajtów / słów, więc są reprezentowane dokładnie tak samo w tabeli funkcji .

_(addbf, RM ADD)      _(addwf, RM ADD)       _(addbt,  RM ADD)     _(addwt, RM ADD)

Tabela funkcji jest tworzona za pomocą tego makra:

OP(OPF)

która dotyczy OPF()każdej reprezentacji kodu operacyjnego. OPF()jest zdefiniowany jako:

#define OPF(a,b)void a(){DW b;}     // generate opcode function

Tak więc pierwsze cztery kody są rozwijane (raz) do:

void addbf(){ DW RM ADD ; }
void addwf(){ DW RM ADD ; }
void addbt(){ DW RM ADD ; }
void addwt(){ DW RM ADD ; }

Funkcje te wyróżniają się wynikiem DWmakra, które określa kierunek i bity bajt / słowo prosto z bajtu opcode. Po rozwinięciu treści jednej z tych funkcji (raz) powstaje:

if(trace){ P("%s:\n",__func__); }  // DW: set d and w from o
d=!!(o&2);
w=o&1;
RMP LDXY  // RM: normal mrm decode and load
z=x+y; LOGFLAGS MATHFLAGS RESULT  // ADD
;

Jeżeli główna pętla już ustawiła ozmienną:

while(!halt){tab[o=fetchb()]();}}

Rozszerzanie jeszcze raz daje całe „mięso” kodu operacyjnego:

// DW: set d and w from o
if(trace){ P("%s:\n",__func__); }
d=!!(o&2);
w=o&1;

// RMP: fetch mrm byte and decode, setting x and y as pointers to args and p ptr to dest
rm r=mrm(fetchb());
x=decreg(r.reg,w);
y=decrm(r,w);
if(trace>1){ P("x:%d\n",x); P("y:%d\n",y); }
p=d?(void*)x:(void*)y;

// LDXY: fetch x and y values from x and y pointers
x=get_((void*)x,w);
y=get_((void*)y,w);
if(trace){ P("x:%d\n",x); P("y:%d\n",y); }

z=x+y;   // ADD
// LOGFLAGS: flags set by logical operators
*fl=0;
*fl |= ( (z&(w?0x8000:0x80))           ?SF:0)
     | ( (z&(w?0xffff:0xff))==0        ?ZF:0) ;

// MATHFLAGS: additional flags set by math operators
*fl |= ( (z&(w?0xffff0000:0xff00))     ?CF:0)
     | ( ((z^x)&(z^y)&(w?0x8000:0x80)) ?OF:0)
     | ( ((x^y^z)&0x10)                ?AF:0) ;

// RESULT: store result to p ptr
if(trace)P(w?"->%04x ":"->%02x ",z);
put_(p,z,w);
;

I w pełni wstępnie przetworzona funkcja, przeszła przez indent:

void
addbf ()
{
  if (trace)
    {
      printf ("%s:\n", __func__);
    }
  d = ! !(o & 2);
  w = o & 1;
  rm r = mrm (fetchb ());
  x = decreg (r.reg, w);
  y = decrm (r, w);
  if (trace > 1)
    {
      printf ("x:%d\n", x);
      printf ("y:%d\n", y);
    }
  p = d ? (void *) x : (void *) y;
  x = get_ ((void *) x, w);
  y = get_ ((void *) y, w);
  if (trace)
    {
      printf ("x:%d\n", x);
      printf ("y:%d\n", y);
    }
  z = x + y;
  *fl = 0;
  *fl |=
    ((z & (w ? 0x8000 : 0x80)) ? SF : 0) | ((z & (w ? 0xffff : 0xff)) ==
                        0 ? ZF : 0);
  *fl |=
    ((z & (w ? 0xffff0000 : 0xff00)) ? CF : 0) |
    (((z ^ x) & (z ^ y) & (w ? 0x8000 : 0x80)) ? OF : 0) |
    (((x ^ y ^ z) & 0x10) ? AF : 0);
  if (trace)
    printf (w ? "->%04x " : "->%02x ", z);
  put_ (p, z, w);;
}

Nie jest to najlepszy styl C do codziennego użytku, ale używanie makr w ten sposób wydaje się być idealne do tego, aby implementacja była tutaj bardzo krótka i bardzo bezpośrednia.

Wyjście programu testowego, z ogonem wyjścia śledzenia:

43(103) incbx:
->0065 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:013e fl:0000 NC NO NS NZ 
83(203) immis:
fb(373) 64(144) x:100
y:101
CMP ->0001 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:0141 fl:0000 NC NO NS NZ 
76(166) jbe:
da(332) <0> 
ax:0020 cx:0015 dx:0190 bx:0065 sp:1000 bp:0000 si:0000 di:00c2 ip:0143 fl:0000 NC NO NS NZ 
f4(364) hlt:

.........                                                                       
Hello, world!                                                                   
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ 


################################################################################
##                                                                            ##
##  0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987                          ##
##                                                                            ##
##  0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400     ##
##                                                                            ##
##  2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97    ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
################################################################################

Udostępniłem kilka wcześniejszych wersji w comp.lang.c, ale nie były one bardzo zainteresowane.



indented 5810 linii.
luser droog,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.