Emule uma CPU Intel 8086

157

Nota: Chegaram algumas respostas. Considere também votar em novas respostas.


O 8086 é o primeiro microprocessador x86 da Intel. Sua tarefa é escrever um emulador para ele. Como isso é relativamente avançado, quero limitar um pouco:

  • Apenas os seguintes opcodes precisam ser implementados:
    • mov, empurrão, pop, xchg
    • adicionar, adc, sub, sbb, cmp e, ou, xor
    • inc, dec
    • chamada, ret, jmp
    • jb, jz, jbe, js, jnb, jnz, jnbe, jns
    • stc, clc
    • hlt, nop
  • Como resultado, você só precisa calcular os sinalizadores de transporte, zero e sinal
  • Não implemente segmentos. Suponha cs = ds = ss = 0.
  • Nenhum prefixo
  • Nenhum tipo de interrupção ou porta IO
  • Nenhuma função de string
  • Nenhum código de operação de dois bytes (0F ..)
  • Sem aritmética de ponto flutuante
  • (obviamente) nada de 32 bits, sse, mmx, ... o que ainda não foi inventado em 1979
  • Você não precisa contar ciclos ou fazer qualquer tempo

Comece com ip = 0e sp = 100h.


Entrada: Seu emulador deve pegar um programa binário em qualquer tipo de formato que você desejar como entrada (ler do arquivo, matriz predefinida, ...) e carregá-lo na memória no endereço 0.

Saída: a RAM de vídeo inicia no endereço 8000h, cada byte é um caractere (ASCII-). Emule uma tela 80x25 para o console. Trate zero bytes como espaços.

Exemplo:

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!...

Nota: Isso é muito semelhante ao modo de vídeo real, que geralmente está em 0xB8000 e possui outro byte por caractere para cores.

Critérios de vitória:

  • Todas as instruções mencionadas precisam ser implementadas
  • Eu fiz um programa de teste não comentado ( link , fonte nasm ) que deve ser executado corretamente. Produz

    .........                                                                       
    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    ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ##                                                                            ##
    ################################################################################
    
  • Não tenho muita certeza se isso deve ser um codegolf; é uma tarefa difícil, então qualquer envio ganhará muitos votos positivos de qualquer maneira. Por favor comente.

Aqui estão alguns links para ajudá-lo nessa tarefa:

Esta é a minha primeira entrada nesta plataforma. Se houver algum erro, indique-o; se eu perdi um detalhe, basta perguntar.

cópia de
fonte
5
muito avançado para mim, mas estou muito ansioso para ver as respostas para essa pergunta, pois é exatamente o tipo de coisa em que estou mais interessado! I pode tomar uma rachadura em que mais tarde se eu estou sentindo particularmente masoquistas ...
Chris Browne
3
@ChrisBrowne boa sorte sendo masoquista! Atualmente, estou transformando meu 8086 em um 80386 e aprendi muito com esse projeto até agora.
copia
2
+1 + favorito ... não consigo expressar o que senti quando vi essa pergunta.
Ixtmixilix
2
@copy Nunca é tarde demais para fazer uma competição de golfe para cada par único idioma / host
Yauhen Yakimovich
2
@ MartinBüttner Claro, a pergunta é mais antiga que essa tag e, basicamente, tem sido um concurso de popularidade
copie

Respostas:

84

Sinta-se à vontade para jogar e jogar golfe: https://github.com/julienaubert/py8086

Resultado Eu incluí um depurador interativo também.

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

Existem três arquivos: emu8086.py (obrigatório) console.py (opcional para saída de exibição), disasm.py (opcional, para obter uma lista do asm no codegolf).

Para executar com a exibição (a nota usa maldições):

python emu8086.py 

Para executar com o depurador interativo:

python emu8086.py a b

Para executar com o "depurador" não interativo:

python emu8086.py a

O programa " codegolf " deve estar no mesmo diretório.

emu8086.py

console.py

disasm.py

No github

ja
fonte
9
Esse é o primeiro post do Code Golf. +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 ...
Dillon Cower
@DC obrigado :) foi um desafio divertido!
ja
1
Ainda não consigo acreditar que alguém realmente fez isso :-) Ótimo trabalho!
copie
1
Surpreendente! Parabéns! Quantas linhas ele teve no final?
que você precisa
59

Haskell, 256 234 196 linhas

Eu tenho esse trabalho em andamento há algum tempo, pretendia polir um pouco mais antes de publicar, mas agora a diversão começou oficialmente, não há muito sentido em mantê-la oculta. Ao extrair, notei que ele tem exatamente 256 linhas, então suponho que esteja em um ponto "notável" de sua existência.

Conteúdo: apenas o conjunto de instruções 8086 é suficiente para executar o exemplo binário na perfeição. O código de modificação automática é suportado. (pré-busca: zero bytes)
Ironicamente, as primeiras iterações suficientes do código foram mais longas e suportaram menos o período do opcode. A refatoração acabou sendo benéfica tanto para o comprimento do código quanto para a cobertura do código de operação.

O que há: obviamente, segmentos, prefixos e opcodes multibyte, interrupções, portas de E / S, operações de string e FP. Inicialmente, segui o PUSH SPcomportamento original , mas tive que abandoná-lo após algumas iterações.

Os resultados de sinalização de transporte provavelmente estão muito confusos em alguns casos de ADC/ SBB.

Enfim, aqui está o código:

------------------------------------------------------------
-- 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)

A saída para o binário de amostra fornecido corresponde perfeitamente à especificação. Experimente usando uma chamada como:

runhaskell 8086.hs <8086.bin

A maioria das operações não implementadas resultará simplesmente em uma falha na correspondência de padrões.

Ainda pretendo fatorar um pouco mais e implementar a saída ao vivo real com maldições.

Atualização 1: reduziu para 234 linhas. Organizei melhor o código por funcionalidade, realinhei o que poderia ser, tentei manter 80 colunas. E refatorou a ALU várias vezes.

Atualização 2: faz cinco anos, imaginei que uma atualização para compilar perfeitamente o GHC mais recente poderia estar em ordem. Pelo caminho:

  • se livrou de liftM, liftM2 e tal. Eu amo ter <$>e <*>no Prelúdio.
  • Data.Bool e Data.ByteString, economiza um pouco e limpa.
  • O registro IP costumava ser especial (não endereçável), agora está no arquivo de registro. Não faz muito sentido 8086, mas ei, eu sou um jogador de golfe.
  • Agora é todo o código puro baseado em ST. Do ponto de vista do golfe, isso é péssimo, porque é necessário fazer muitas assinaturas de tipo. Por outro lado, tive uma briga com a consciência e perdi, então agora você recebe o código longo e limpo.
  • Então agora isso é rastreado pelo git.
  • Adicionados comentários mais sérios. Como conseqüência, a maneira como conto as linhas mudou: estou descartando linhas vazias e com comentários puros. Por meio deste, garanto todas as linhas, mas as importações têm menos de 80 caracteres. Não estou descartando assinaturas de tipo, pois a que resta é realmente necessária para compilar corretamente (muito obrigado pela limpeza do ST).

Como os comentários do código dizem, 5 linhas (a importação do Data.Char, os mapeamentos de registro de 8 bits e o despejo de tela) estão fora das especificações, então você pode descontá-las se sentir vontade :-)

JB
fonte
3
Agradável. É muito curto, especialmente se comparado à minha solução e à outra. Seu código também parece muito bom, embora eu precise aprender primeiro o Haskell.
copie
3
Bom trabalho! Muito curto. Eu deveria aprender haskell.
ja
O que é .|.? / 10char
Soham Chowdhury
@octatoan a operação conhecida em x86 opcodes como OR.
JB
46

Linhas C - 7143 (própria CPU 3162 linhas)

EDIT: A compilação do Windows agora possui menus suspensos para alterar os discos virtuais.

Eu escrevi um emulador de PC 80186 / V20 completo (com CGA / MCGA / VGA, blaster de som, adlib, mouse etc.), não é uma coisa trivial imitar um 8086 de qualquer maneira. Demorou muitos meses para ser totalmente preciso. Aqui está o módulo da CPU apenas fora do meu emulador.

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

Serei o primeiro a admitir que uso muitas variáveis ​​globais neste emulador. Comecei a escrever isso quando ainda era novo em C, e isso mostra. Preciso limpar um pouco desses dias. A maioria dos outros arquivos de origem não parece tão feia.

Você pode ver todo o código (e algumas capturas de tela, uma abaixo) por aqui: http://sourceforge.net/p/fake86

Eu ficaria muito feliz em ajudar qualquer pessoa que queira escrever por conta própria, porque é muito divertido e você aprende muito sobre a CPU! Isenção de responsabilidade: eu não adicionei a emulação 8080 do V20, pois quase nunca foi usada em um programa para PC. Parece muito trabalho sem lucro.

Street Fighter 2!

Mike C
fonte
3
Bom trabalho! Os jogos realmente rodam a toda velocidade?
copie
1
Obrigado. Sim, ele roda muitas vezes mais rápido que um 8088. Em um sistema moderno, ele pode executar velocidades semelhantes a 486. Em um processador realmente bom, é como um Pentium low-end. Infelizmente, emular uma CPU não pode ser multithread realmente. Eu faço toda a renderização de vídeo em seu próprio segmento. Também já o executei no meu antigo PowePC G3 de 400 MHz, com as velocidades de 8088 verdadeiras.
Mike C
1
Impressionante! Eu também queria implementar mais códigos operacionais e segmentação; no entanto, não foi possível encontrar muitos programas de teste para executar nele. Você baixou roms antigos?
Dave C
1
Dave, não há realmente uma séria falta de ROMs de teste 8086 por aí, surpreendentemente, como você descobriu também. O jeito que eu fiz foi começar fazendo uma ROM XT BIOS genérica funcionar corretamente. Se isso funcionar, sua segmentação provavelmente está correta. Depois disso, ele estava apenas depurando até o DOS começar a funcionar ... e depois para aplicativos e jogos! :)
Mike C
1
@ MikeC Eu gostaria de alguma ajuda para iniciantes ou sugestões! (Chalaça pretendida: P). Sou desenvolvedor de aplicativos para desktop e Web há muitos anos e, lentamente, cheguei a um ponto em que tenho o código-fonte do Linux. Eu geralmente entendo como várias partes de um sistema operacional funcionam e eu pude brincar com pequenos projetos de sistemas operacionais de brinquedo. Mas interagir com hardware direto me escapa!
Gideon
41

Postscript (130 200 367 517 531 222 246 linhas)

Ainda um trabalho em andamento, masEu queria mostrar algum código em um esforço para incentivar outras pessoas a mostrar algum código .

O conjunto de registradores é representado como uma sequência, de modo que os vários registradores de tamanho de bytes e palavras podem se sobrepor naturalmente, referindo-se a substrings. As seqüências de caracteres são usadas como ponteiros, para que um registro e um local de memória (substring da sequência de memória) possam ser tratados uniformemente nas funções do operador.

Depois, há algumas palavras para obter e armazenar dados (bytes ou palavras) de um "ponteiro", da memória, de mem [(IP)] (aumento de IP). Depois, existem algumas funções para buscar o byte MOD-REG-R / M e definir as variáveis ​​REG e R / M e MOD e decodificá-las usando tabelas. Em seguida, o operador funciona, digitado no byte opcode. Portanto, o loop de execução é simples fetchb load exec.

Eu só tenho um punhado de opcodes implementados, mas gObter a decodificação do operando parecia um marco que eu queria compartilhar.

editar: palavras adicionadas para estender números negativos. Mais opcodes. Correção de bug nas atribuições de registro. Comentários. Ainda trabalhando em bandeiras e preenchendo os operadores. A saída apresenta algumas opções: saída de texto para stdout na terminação, saída contínua usando códigos vt100, saída para a janela de imagem usando a fonte CP437.

editar: gravação concluída, iniciada a depuração. Obtém os quatro primeiros pontos de saída! Então o transporte dá errado. Sonolento.

edit: Eu acho que tenho a bandeira de transporte classificada. Parte da história aconteceu em comp.lang.postscript . Adicionei alguns aparelhos de depuração e a saída vai para a janela de gráficos (usando minha fonte Code-Page Type-3 Type-3 escrita anteriormente ), para que a saída de texto possa estar cheia de traços e despejos. Ele escreve "Olá Mundo!" e depois há aquele sinal de suspeita. Então muito mais nada. :( Vamos chegar lá. Obrigado por todo o incentivo!

editar: executa o teste até a conclusão. Os poucos bugs finais foram: XCHG fazendo 2 {ler loja} repita as cópias, é claro, em vez de trocas, E não definindo sinalizadores, (FE) INC tentando obter uma palavra de um ponteiro de bytes.

editar: Total de reescritas do zero usando a tabela concisa do manual ( virou uma nova página! ). Estou começando a pensar que levar em consideração a loja dos opcodes era uma péssima idéia, mas isso ajudou a manter o optab bonito. Nenhuma captura de tela desta vez. Eu adicionei um contador de instruções e um mod-trigger para despejar a memória de vídeo, para que ela se interceda facilmente com as informações de depuração.

edit: Executa o programa de teste novamente! Os poucos bugs finais para a reescrita mais curta estavam deixando de estender o byte imediato nos opcodes 83 (grupo "Imediato") e EB (JMP curto). O aumento de 24 linhas abrange rotinas adicionais de depuração necessárias para rastrear os erros finais.

%!
%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

E a saída (com o final da saída de depuração abreviada).

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>
luser droog
fonte
5
Gostaria de saber ... É a tecla de atalho para fechar um aplicativo alt-F4 porque F4h é o opcode 8086 HLT?
Luser droog
5
Eu só quero lhe dizer que você é absolutamente incrível por implementar isso no Postscript.
cemper93
1
Esse código é curto . Merece mais votos. Tenha o meu, para começar.
JB
2
espera ... postscript é uma linguagem de programação ?! ;)
n611x007 30/03
32

Javascript

Estou escrevendo um emulador 486 em javascript inspirado no jslinux. Se eu soubesse quanto trabalho seria, provavelmente nunca teria começado, mas agora quero finalizá-lo.

Então me deparei com seu desafio e fiquei muito feliz por ter um programa 8086 para testar.

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

Você pode "vê-lo" ao vivo aqui: http://codinguncut.com/jsmachine/

Eu tive um problema ao imprimir o buffer gráfico. Onde deveria haver espaços, a memória contém elementos "00". É correto interpretar "0x00" como espaço ou eu tenho um bug no meu emulador?

Felicidades,

Johannes

fluido
fonte
Interessante, eu realmente sei o seu nome nos seus Screencasts, que assisti após a resposta de Haskell neste desafio (e também iniciei um emulador x86 em Javascript). Sim, zero bytes deve aparecer como espaços. Também adicionei a captura de tela à sua postagem. +1 de qualquer maneira :-)
copie
@ Johannes Eu dei uma olhada rápida no código mycpu-min.js. Pelo que posso dizer, você usou apenas algumas idéias do cpux86.js (do jslinux do FB). Parabéns! Um bom trabalho. Alguma chance de ver o mycpu.js não compilado em algum lugar? Esperançosamente em github.com/codinguncut
Yauhen Yakimovich
@YauhenYakimovich Não, não reutilizei nenhum código jslinux. Eu implementei até agora todas as 286 instruções menos paginação e segmentação (mmu). Meu plano era liberar o código sob a GPL, mas eu realmente gostaria de comercializar a ideia de rodar, por exemplo, Freedos ou Reactos, por isso ainda não tenho certeza sobre o licenciamento. A verdade é que levarei muito tempo para implementar o gerenciamento de memória cheia. e, em seguida, muito tempo para fazê-lo funcionar em alta velocidade. Definitivamente vou compartilhar em github.com/codinguncut. Obrigado pelo seu feedback, Johannes
fluquid
1
O link está quebrado para mim. (IE no Windows 8)
Conor O'Brien
Isso chega muito tarde. O caractere zero é outro espaço na RAM de vídeo.
1937 Joshua
30

C ++

Gostaria de enviar nossa entrada para este desafio de código. Foi escrito em c ++ e executa o programa de teste perfeitamente. Implementamos 90% dos códigos operacionais de um byte e da segmentação básica (alguns desativados porque não funcionam com o programa de teste).

Gravação do programa: http://davecarruth.com/index.php/2012/04/15/creating-an-8086-emulator

Você pode encontrar o código em um arquivo zip no final da postagem do blog.

Screenshot executando o programa de teste: insira a descrição da imagem aqui

Demorou um pouco de tempo ... se você tiver quaisquer perguntas ou comentários, sinta-se à vontade para me enviar uma mensagem. Certamente foi um ótimo exercício de programação para parceiros.

Dave C
fonte
3
É sempre bom quando as pessoas se divertem nesse desafio :) Apenas algumas notas: Meu programa de teste deve funcionar (e foi testado) com todos os segmentos zerados. Observando parte do seu código, notei que a ret imminstrução está errada (veja aqui ) e você está perdendo o 0xffgrupo. Eu gosto das suas mensagens de erro: throw "O valor imediato não pode armazenar um valor, retarde.";
copie
Tivemos dois problemas principais com o programa de teste: 1) Segmentação - quando há uma CHAMADA, estávamos pressionando o CS na pilha ... uma das funções no programa de teste não foi assim. 2) O programa de teste esperava que nossa memória fosse inicializada em zero. Enfim, nos divertimos muito, muito obrigado por postar!
Dave C
Você pode ter cometido um erro: Os saltos próximos ( 0xE8 ) não pressionam o csregistro
copie
Esse seria o problema, boa captura! Você parece muito experiente com o 8086, você programou?
Dave C
1
Na verdade, estou trabalhando sozinho em um projeto de emulador x86. Ele está rodando muito bem e atualmente trabalho com suporte total de 32 bits; simplesmente não postou aqui porque pode não ser justo com outros pôsteres (e o código fonte está um pouco confuso).
copie
28

C

Grande desafio e meu primeiro. Criei uma conta apenas porque o desafio me intrigou muito. O lado ruim é que eu não conseguia parar de pensar no desafio quando tinha trabalho real, pagador e de programação para fazer.

Sinto-me compelido a executar uma emulação 8086 completa, mas esse é outro desafio ;-)

O código está escrito em ANSI-C, então apenas compile / vincule os arquivos .c, passe o binário do codegolf e vá.

fonte compactada

insira a descrição da imagem aqui

RichTX
fonte
Bom trabalho RichTX!
Dave C
Obrigado Dave. Você também. Eu não entendia a expectativa de tornar o código o menor possível quando comecei, mas ainda era um desafio.
RichTX
+1 Espiei seu código para descobrir como a bandeira de transporte funciona.
Luser droog 3/12/12
O link está desativado para mim.
Tyilo
25

C ++ 1064 linhas

Projeto fantástico. Eu fiz um emulador da Intellivision há muitos anos, por isso foi ótimo flexionar meus músculos novamente.

Após cerca de uma semana de trabalho, eu não poderia estar mais animado quando isso aconteceu:

.........
╤╤╤╤╤╤╤╤╤╤╤╤╤╤
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, E d E f d i ↓ 8 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 de d dd de ☺e ☻e ♥ e ♦ e e e ♠ e
 ee ef ☺f ♥f ♥ f ♦ f ♣ f ff fg ☺g gg ♥ g ♦ g ♣ g ♠ gggh ☺h ♥h ♥
h hi h hi hi hi hi hi ♥ ♥ ♥ ♥ ♥ ♥ ♥ ♥

Um pouco de depuração mais tarde e ... SHAZAM! insira a descrição da imagem aqui

Além disso, reconstruí o programa de teste original sem as extensões 80386, pois queria criar meu emulador fiel ao 8086 e não falsificar nenhuma instrução extra. Link direto para o código aqui: Arquivo Zip .

Ok, estou me divertindo muito com isso. Interrompi o gerenciamento de memória e tela, e agora a tela é atualizada quando o buffer de tela é gravado. Eu fiz um vídeo :)

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

Atualizações: a primeira passagem da segmentação está em vigor. Poucas instruções são realmente implementadas, mas eu a testei movendo o CS / DS e SS, e tudo ainda funciona bem.

Também foi adicionado um tratamento rudimentar de interrupção. Muito rudimentar. Mas eu implementei int 21h para imprimir uma string. Adicionadas algumas linhas à fonte de teste e carregadas também.

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

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

insira a descrição da imagem aqui

Se alguém tiver algum código de montagem bastante simples que teste os segmentos, eu adoraria brincar com ele.

Estou tentando descobrir até onde quero levar isso. Emulação de CPU completa? Modo VGA? Agora estou escrevendo o DOSBox.

12/6: Confira, modo VGA!

insira a descrição da imagem aqui

JoeFish
fonte
Alguma chance de você poder publicar seu código em um site gratuito que não exija registro? Graças
Dave C
Não sabia que isso exigia registro. Me desculpe por isso! Vou tentar fazer isso quando chegar em casa hoje à noite.
31412 JoeFish
@DaveC, verifique a edição mais recente.
JoeFish
Gostaria de saber se existe um porto camelForth. Isso testaria os segmentos.
Luser droog 3/12/12
Fantástico! +1 novamente. Aliás, não é uma porta de camelo por diante 8086 bradrodriguez.com/papers/index.html .
Luser droog,
25

C ++ - 4455 linhas

E não, não fiz apenas os requisitos da pergunta. Eu fiz o INTEIRO 8086, incluindo 16 opcodes CONHECIDOS nunca antes. reenigne ajudou a descobrir esses códigos.

https://github.com/Alegend45/IBM5150

Darius Goad
fonte
onde está o arquivo de 4455 linhas? ah, eu encontrei. o #include "cpu.h"é difícil de ver.
Luser droog
2
(w) declaração de mudança sagrada!
Luser droog
Sim, também está prestes a piorar, já que também estou incluindo o suporte ao NEC V20.
precisa saber é o seguinte
Eu olhei no blog de reenigne . Não foi possível encontrar nada sobre esses códigos adicionais. Está online em algum lugar?
Luser droog
1
Ele não atualiza seu blog há algum tempo. Ele está no # ibm5150 na EFNET, então você pode perguntar a ele lá.
precisa saber é o seguinte
20

Javascript - 4.404 linhas

Eu me deparei com este post ao pesquisar informações para o meu próprio emulador. Este post sobre Codegolf foi absolutamente inestimável para mim. O programa de exemplo e o conjunto associado tornaram possível depurar facilmente e ver o que estava acontecendo.

Obrigado!!!

E aqui está a primeira versão do meu emulador Javascript 8086.

Execução concluída

Recursos:

  • Todos os códigos de operação necessários para esse desafio, além de alguns extras que eram semelhantes o suficiente para serem fáceis de codificar
  • Vídeo em modo de texto parcialmente funcional (80x25) (sem interrupções ainda)
  • Pilha de funcionamento
  • Memória básica (não segmentada)
  • Depuração bastante decente (tenho que ter isso)
  • O conjunto de fontes Code Page 437 é carregado dinamicamente a partir de uma representação de bitmap

Demo

Eu tenho uma demo online, sinta-se à vontade para jogar com ela e me avise se você encontrar bugs :)

http://js86emu.chadrempp.com/

Para executar o programa codegolf

1) clique no botão de configurações

insira a descrição da imagem aqui

2) clique em carregar (você pode jogar com as opções de depuração aqui, como percorrer o programa). O programa codegolf é o único disponível no momento, estou trabalhando para ficar mais online.

insira a descrição da imagem aqui

Fonte

Fonte completa aqui. https://github.com/crempp/js86emu

Tentei colar aqui as tripas da emulação 8086 (como sugerido pela maçaneta da porta), mas excedeu o limite de caracteres ("O corpo é limitado a 30000 caracteres; você inseriu 158.272").

Aqui está um link rápido para o código que eu ia colar aqui - https://github.com/crempp/js86emu/blob/39dbcb7106a0aaf59e003cd7f722acb4b6923d87/src/js/emu/cpus/8086.js

*Edit - updated for new demo and repo location

crempp
fonte
Wow maravilhoso! No entanto, seria ideal se o código estivesse em sua própria postagem, pois preferimos que nossas postagens sejam independentes.
Maçaneta
@ Doorknob, não sei se entendi. Você gostaria que eu publicasse 4.400 linhas de código em linha, na postagem?
Crempp
Umm ... eu não sabia que era tanto tempo. Ele se encaixa dentro do limite máximo de caracteres? Nesse caso, sim, seria ótimo se sua postagem fosse independente. Obrigado! :-)
Maçaneta da porta
13

Java

Eu queria fazer esse desafio por tanto tempo e finalmente tirei um tempo para fazê-lo. Foi uma experiência maravilhosa até agora e estou orgulhoso de perceber que finalmente a completei.

Saída do Programa de Teste

Fonte

O código fonte está disponível no GitHub em NeatMonster / Intel8086 . Tentei documentar praticamente tudo, com a ajuda do holly 8086 Family Manual do Usuário .

Pretendo implementar todos os opcodes e recursos ausentes, portanto, você pode conferir o release 1.0 para uma versão com apenas os necessários para este desafio.

Muito obrigado a @copy!

NeatMonster
fonte
13

Common Lisp - 580 loc (442 sem linhas em branco e comentários)

Eu usei esse desafio como uma desculpa para aprender o Common Lisp. Aqui está o resultado:

;;; 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)))

Aqui está a saída no Emacs:

Janela do Emacs com dois painéis, uma seção da fonte Lisp à esquerda e a saída REPL com o conteúdo necessário à direita.

Eu quero destacar três características principais. Esse código faz uso intenso de macros ao implementar instruções, comomov , xchge as operações artithmetic. Cada instrução inclui uma disasm-instrchamada de macro. Isso implementa a desmontagem ao lado do código real usando uma variável global if sobre uma definida em tempo de execução. Estou particularmente orgulhoso da abordagem agnóstica do destino usada para escrever valores em registros e endereços indiretos. As macros que implementam as instruções não se importam com o destino, pois os formulários emendados para qualquer possibilidade funcionarão com a setfmacro Common Lisp genérica .

O código pode ser encontrado no meu repositório GitHub . Procure o ramo "codegolf", pois eu já comecei a implementar outros recursos do 8086 no master. Eu já implementei os sinalizadores de excesso e paridade, junto com o registro FLAGS.

Existem três operações neste, não no 8086, o 0x820x83 versões e dos operadores lógicos. Isso foi pego muito tarde e seria muito complicado remover essas operações.

Gostaria de agradecer ao @ja por sua versão em Python, que me inspirou no início deste empreendimento.

happy5214
fonte
3
Primeira resposta incrível! Bem-vindo ao site :)
DJMcMayhem
1
Escolha de idioma muito legal!
copie
12

C - 319 348 linhas

Esta é uma tradução mais ou menos direta do meu programa Postscript para C. É claro que o uso da pilha é substituído por variáveis ​​explícitas. Os campos de uma instrução são divididos nas variáveis o- instrução opcode byte, d- direction field, w- width field. Se for uma instrução "mod-reg-r / m", o byte mr-rm é lido struct rm r. A decodificação dos campos reg e r / m prossegue em duas etapas: calculando o ponteiro para os dados e carregando os dados, reutilizando a mesma variável. Então, para algo como ADD AX,BX, primeiro x é um ponteiro para ax e y é um ponteiro para bx, depois x é o conteúdo (ax) e y é o conteúdo (bx). Há muita conversão necessária para reutilizar a variável para diferentes tipos como este.

O byte opcode é decodificado com uma tabela de ponteiros de função. Cada corpo de função é composto usando macros para peças reutilizáveis. A DWmacro está presente em todas as funções do opcode e decodifica as variáveis de wdo obyte do opcode. A RMPmacro executa o primeiro estágio de decodificação do byte "mr-rm" e LDXYexecuta o segundo estágio. Os códigos de operação que armazenam um resultado usam a pvariável para manter o ponteiro no local do resultado e a zvariável para manter o valor do resultado. Os sinalizadores são calculados após o zvalor ser calculado. As operações INCe DECsalvam o sinalizador de transporte antes de usar a MATHFLAGSfunção genérica (como parte do ADDouSUB submacro) e restaure depois, para preservar o Carry.

Edit: bugs corrigidos!
Editar: expandido e comentado. Quando trace==0ele agora gera um comando ANSI mover para 0,0 ao descarregar o vídeo. Por isso, simula melhor uma exibição real. A BIGENDIANcoisa (que nem funcionou) foi removida. Em alguns lugares, ele depende da ordem de bytes little-endian, mas pretendo corrigir isso na próxima revisão. Basicamente, todo acesso de ponteiro precisa passar pelas funções get_e put_que explicitamente (des) compõem os bytes na ordem 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

O uso de macros para os estágios das várias operações resulta em uma correspondência semântica muito próxima da maneira como o código postscript opera de maneira puramente seqüencial. Por exemplo, os quatro primeiros opcodes, 0x00-0x03, são todas instruções ADD com direção variável (REG -> REG / MOD, REG <- REG / MOD) e tamanhos de bytes / palavras, portanto, são representados exatamente da mesma forma na tabela de funções .

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

A tabela de funções é instanciada com esta macro:

OP(OPF)

que se aplica OPF()a cada representação opcode. OPF()é definido como:

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

Portanto, os quatro primeiros opcodes se expandem (uma vez) para:

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

Essas funções se distinguem pelo resultado da DWmacro que determina a direção e os bits de byte / palavra diretamente do byte do código de operação. Expandir o corpo de uma dessas funções (uma vez) produz:

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
;

Onde o loop principal já definiu a ovariável:

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

Expandir mais uma vez fornece toda a "carne" do código de operação:

// 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);
;

E a função totalmente pré-processada passou por 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);;
}

Não é o melhor estilo C para uso diário, mas o uso de macros dessa maneira parece bastante perfeito para tornar a implementação aqui muito curta e direta.

Saída do programa de teste, com cauda da saída do rastreio:

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    ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
##                                                                            ##
################################################################################

Compartilhei algumas versões anteriores no comp.lang.c, mas elas não estavam muito interessadas.

luser droog
fonte
Versão macro-expandida Embrulhado . (Isso não ajuda :)
luser droog
indented 5810 linhas.
Luser droog