Online 8086 Emulator
yjdoc2.github.io
yjdoc2.github.io
I tried to enter a "db CDh, 20h" which is the second-shortest valid DOS program.
- The "DB" instruction does not support multiple operands
- Programs needs to have start labels
- Start label can't point to bytes defined by "DB"
- NOP instruction not recognized
- DB cant be used without a label??
So i changed my mind and tried to write a simple 'ret' one-byte program. Which is a valid way to exit a program in DOS and CP/M.
Unrecognized token `` found at 0:3 Expected one of Error : ret is encountered without corresponding call
This is not an error in assembly!Also the terms. "compiling" is the process that generates assembly from a higher language. "assembling" is the process of turning assembly into machine code.
Whatever this is, this is not for me.
Even on a non-DOS 8086 platform, you would get the instructions for it to assemble.
http://www.ctyme.com/intr/rb-0210.htm
Long time no see Ralf Brown's Interrupt List...
https://github.com/YJDoc2/8086-Emulator/blob/master/src/lib/...
edit
Here's how to define a 'procedure', which is different from a 'label' and can be called:
def count_up {
INC AX
CALL count_up
}
start:
XOR AX,AX
CALL count_up
Now the 'C'ultists are polluting assembly language with their curly braces! Notice that SP doesn't change while it counts up, the call stack exists only inside the interpreter.I actually had a calculator as a kid that could be programmed in both BASIC and a similar pretend-assembler.
The decision to differentiate procedures from labels was intentional, and taken due to some restrictions of our IR (which is not the best, issue is open to change that).
Also, the way to define procedure is this way (separate form labels), as I felt that might be a bit more familiar to newcomers to assembly, who are coming from other languages.
Call stack exists inside assembler due to the same reason, as in case someone accidentally bashes their stack , it would still not corrupt the whole code. Maybe we should do a better job at reporting this so users can know they have corrupted the stack, instead of silently ignoring.
The SP should be changing, it is probably a bug. Thanks for reporting, I'll try to take a look at it.
Since the code doesn't have an address inside the emulated memory space, it kind of makes sense for there to be a separate return stack. Problem is that you're trying to emulate 8086 and not a Forth machine :)
One solution might be to have 'CALL' decrement SP (perhaps writing some dummy value like 0000 or FFFF to memory), and also remember where SS:SP points now. The interpreter's return stack could store that address, along with the reference to the line of code it should return to.
When executing 'RET', check if the stack pointer is at the same location as it was after the last 'CALL', and halt with an error message if it is different. I suggest you also allow 'JMP' to procedures, or just get rid of the separation entirely and make everything a label. The procedure syntax could still be allowed as an alternative but just declare a normal label.
Another small but impactful bug: REP seems to always perform one more iteration when CX is zero!
Wow. Reading the authors other comments, it sounds like they couldn't find an assembler they liked for their operating system, and instead decided to spend their course building their own instead, as they imagined it would work. Stay away from this tool if your goal is to learn about 8086. This project is an 8086-inspired programming language.
It is clearly a student project, intended to run very simple code and show how the registers and memory are affected. They did read the Intel manual, and most of the instructions seem to be implemented correctly including flag behaviour; for example the SBB AL,0x69 + DAS trick works.
Generating and running actual machine code might well be "out of scope" for something like this.
(segment-register) [bs/bp/si/di , signed word number]; The offset is taken from the registers, and the number is added to it
(segment-register) [bs/bp , si/di , (signed word number) ];It's in the "Memory" part of the instructions page.
This was originally made primarily for our own use, so I can definitely understand that the choices we made for this is not everyone's cup of tea.
However, I would just like to reply to the points, in case anyone else is also facing the same things.
- DB indeed doesn't support setting multiple different values, and that is definitely a thing we should change when we update his. Alternatively you can set multiple bytes to a same value using `DB [value,count]`
- Yes, this is intentional. As this emulator is not a "true" 8086 emulator that can just start from any memory address, we need a start label, much similar to how C needs a main function.
- Again, intentional. DB is strictly for data storage, and the whole code is (internally) split into data section (at start) and code section (after data section). Thus you cannot define DB once you start the code section. And as said, start indicates entry point of code, so any DB instruction cannot have start label.
- NOP is not implemented, and is an open issue at the repo.
- DB can be used without a label. for example ``` db [7,5] DW 0x7857 start: mov byte[0], 5
``` is a valid and compiling code. In case you had any issues using DB without label, feel free to comment here, or open an issue on github.
- Yes, ret cannot be used outside procedures. This is again intentional. As this emulator was aimed at students and newcomers to assembly, we wanted to kind of safeguard from accidentally using ret outside of a procedure context. In real assembly, it will let you use ret anywhere, but this does not.
> Also the terms. "compiling" is the process that generates assembly from a higher language. "assembling" is the process of turning assembly into machine code.
You are very much correct on this one. However, in this particular case I would suggest use of 'compiling' is also correct. As noted, this is an emulator of 8086, and we use an string based IR generated from the input assembly code. (This is not the best choice in hind-sight and there is an open issue for changing this). In that sense, we are indeed "compiling" the assembly code you have given to an executable/runnable representation, which then the emulator (which is basically an interpreter in this case) can run.
That said, I do agree that in general, (not in particular context of this emulator) "compiling" is not suited.
---
All said, This might not be everyone's choice for emulator, and I am glad that you gave it a try. Thanks!
This decision seems more likely to teach newcomers the wrong things, like expecting the following code to work:
def plus {
; add two numbers on stack, return result on stack
pop ax
pop bx
add ax,bx
push ax
ret
}
To catch errors like this, you might want to implement a "shadow stack" in the interpreter that keeps track of the type of each stack element (number or return address).edit, some more thoughts on how to implement this:
https://news.ycombinator.com/item?id=35517012
/edit
But the most essential feature of assembly / machine language is that there is in reality no distinction between characters or numbers and memory addresses, procedures and labels, or between code and data. Even compiler-generated code often has jumps to procedures (tail-call optimization).
Also by not exposing any code addresses, your virtual machine becomes less powerful than C - how would you call a function pointer?
A bit of background on this is : we made this when we were students ourselves, learning about 8086, and couldn't find a decent emulator to do some of our lab work (or at least couldn't find a decent one with nice linux support).
We decided to write this primarily for ourselves, and for anyone who want to have an introduction to writing assembly in a friendly environment. This has mistakes, some decisions that seemed good at that time, but are quite bad in hind-site, and you are welcome to open issues on the respective github repos (github.com/YJDoc2/8086-Emulator for the syntax and emulator stuff, github.com/YJDoc2/8086-Emulator-web for the web ui).
We are now busy with other stuff, so the development is not as active as when we initially made this ; however, we try to update and add features, try to do bug fixes, and welcome contributors as much as we can.
Hope this project makes some newcomer interested in amazing domain of assembly, low level systems and system programming!
I like a bit of juice with my product but this feels like it's gone through a hydraulic press!
For me it was the animated coach marks popping up with such amazingly-contextual information nuggets as:
> Step 1: Write your code in the editor
It is intentionally so basic, as we wanted to make sure that any user, including those who have never written any assembly code before , can get a gist of it at the start.
That said, the ` Step 1: Write your code in the editor ` might be a bit overkill :smile:
If you have any suggestions on how we can improve the tutorial so new-comers can find it useful, and at the same time, it is not annoying, we would be very happy to hear it in a github issue.
Thanks!
SET 0x1 ; set addresss for segment 2 dest:DB [0,3] ; store data
; actual entry point of the program start: print mem 0:8 ; print initial state of segment 1 print mem 0x10:8 ; print initial state of segment 2
MOV AX, 0 ; move address of seg1 MOV DS,AX ; to ds MOV AX , 0x1 ; move address of seg2 MOV ES,AX ; to es MOV SI, OFFSET src ; move offset of source data MOV SI, OFFSET dest ; move offset of destination data MOV CX, 0x3 ; move number of data items print reg ; print state of registers
_loop: mov AH, byte DS[SI] ; move one byte from source to ah mov byte ES[DI],AH ; move ah to destination inc SI inc DI dec CX ; decrement count jnz _loop ; if count is not zero jump back print mem 0:8 ; print final state of segment 1 print mem 0x10:8
> Greetings.I am here for your questions
what is the les instruction?
> I missed what you said. What was that?
what is mov?
> What was that?
what is 8086?
> I didn't get that. Can you say it again?
We would really like to hear your thoughts on how we can improve this in a github issue, and we don't have much idea on LLMs, so there is a lot of room for improvements.
In the meantime, you can check the instructions page which lists the information and syntax/usage for them https://yjdoc2.github.io/8086-emulator-web/help
Hope this helps!
This syntax is intentionally verbose, so anyone new to assembly wouldn't mistakenly store word when byte was intended.
You can also check the complete supported syntax here : https://yjdoc2.github.io/8086-emulator-web/help in data transfer, mov section.
Hope this helps!
It's not a PC emulator (or even x86 really, it just interprets the mnemonics you write instead of actual machine code).
When designing this, we never intended to have support for external chips, so instead we designed this to have the full 1 MB of memory.
Hope this helps!
0b11111111111111110000 + 0b1111111111111111 + 1 = 0x10fff0 = 1mb+64kb-16 (for 8086)Some old Microsoft runtime library adjusted the data segment in order to place variables descending from the top offset. If the program was loaded below 64K, the segment value would become 'negative', which was not a problem until the 286 added more address lines. The main reason for the A20 gate hack was to fix this.
(+ what the string function said: It really was 1MB.)