While I'm impressed at the actual emulator, it does rather feel like someone turned on literally every animation and special effect their web framework had to offer.
I like a bit of juice with my product but this feels like it's gone through a hydraulic press!
Hey, yes, the initial tutorial can feel a bit too hand-holding. However, it is only one-time and should not reappear once done.
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.
Hey, Sorry about that! Maybe we should have dialed the reactive stuff down a bit!
On a side note, if you'd prefer a no-gui setup, this also as a commandline based emulator github.com/YJDoc2/8086-Emulator/ . (Not that this is any excuse for the we ui)
Infuriatingly differently from the 8086 i used to program for.
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.
Ooops, yeah it probably needs some work. I _think_ it is the work of a student and
I found it surprisingly beautiful and well done. The actual grammar (which is how I stumbled upon this project) is here:
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.
>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!
> Looks like it's not emulating x86 at all, just interpreting assembly mnemonics as if they were BASIC.
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.
I was a bit disappointed too, but wouldn't be quite as negative:
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) ];
Hey, I can totally understand that this did not work out for you. I am glad nonetheless that you gave it a try anyways, and mentioned the issues you faced, so we can improve on them. Thanks for that!
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!
>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.
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:
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?
Hey, thanks! Happy to hear that you think this is interesting.
Also, your project is amazing! The design is really beautiful, and the features are really awesome! Thanks a lot for sharing this :)
One of the best things from my childhood was writing operating systems in Emu8086 https://youtu.be/flj4PDalKrc I'm glad to see we finally have a web based version! For a moment, I was excited and thought the little ball in the bottom right hand corner was an LLM. Although it looks like it needs more work:
Hey, sorry to disappoint you, but that is the least "working" feature of this. :sweat_smile: It was a last-minute addition, not well trained, and should ideally be removed, but still is there.
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.
Hey, the 8086 has 20 bit memory bus, so it can theoretically support full 1 MB of memory. However, as much as I know, 8086 chips never had complete 1 MB,they had at the most 256/512 KB of memory, and rest address space was used instead as memory mapped IO, or to interface with other chips.
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.
Nope. The 8086 only had 20 physical address lines, so any address beyond FFFF:000F would wrap around to low memory.
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.
Hey, one simple example is `mov byte[0], 5` . You need to give relative memory address (wrto DS segment value) in the brackets, and specify whether you want to store a byte or word length value.
This syntax is intentionally verbose, so anyone new to assembly wouldn't mistakenly store word when byte was intended.
Hey, I am one of the creators of this emulator!
Thanks nhatcher for posting this, glad to see you found this interesting.
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!
SET 0 ; set address for segment 1
src:DB 0x3 ; store data
DB 0x5
DB 0x7
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
44 comments
[ 0.18 ms ] story [ 94.7 ms ] threadI 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!
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.
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:
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.
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:
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?
> 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!
It's not a PC emulator (or even x86 really, it just interprets the mnemonics you write instead of actual machine code).
https://en.wikipedia.org/wiki/X86_memory_segmentation
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!
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.)
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!
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!
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