├── configure.sh ├── .gitignore ├── bochsrc ├── deploy.sh ├── std ├── defines.asm ├── disk.asm ├── video.asm ├── macros.asm ├── writedisk.asm ├── readdisk.asm ├── stdio.asm └── string.asm ├── doc ├── memory_map.md └── plum.md ├── Makefile ├── src ├── boot.asm ├── app.asm ├── kernel.asm └── plum.asm ├── run.sh └── README.md /configure.sh: -------------------------------------------------------------------------------- 1 | apt-get install nano make build-essentials nasm qemu 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cdiso/ 3 | bin/ 4 | obj/ 5 | iso/ 6 | *.o 7 | *.swp 8 | *.bin 9 | kernel.map 10 | # still not ready 11 | src/stage2.asm 12 | script.gdb 13 | syslinux.cfg 14 | -------------------------------------------------------------------------------- /bochsrc: -------------------------------------------------------------------------------- 1 | display_library: sdl 2 | megs: 128 3 | clock: sync=realtime, time0=local 4 | floppya: 1_44=./build/project_e.img, status=inserted 5 | boot: floppy 6 | mouse: enabled=0 7 | magic_break: enabled=1 8 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -eq "1" ]; then 4 | unetbootin lang=en method=diskimage imgfile=build/project_e.img cfgfile=syslinux.cfg installtype=USB targetdrive=$1 nodistro=y autoinstall=yes 5 | else 6 | echo "Need a single argument : the disk where the ISO should be deployed" && exit 1 7 | fi 8 | -------------------------------------------------------------------------------- /std/defines.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_defines_asm 2 | %define proj_e_defines_asm 3 | 4 | %define KERNEL_LOAD_ADDRESS 0x0100 5 | ;%define proj_e_print KERNEL_LOAD_ADDRESS:0x0006 6 | ;%define proj_e_reboot KERNEL_LOAD_ADDRESS:0x0013 7 | ;%define proj_e_waitkeypress KERNEL_LOAD_ADDRESS:0x0018 8 | ;%define proj_e_print_hex KERNEL_LOAD_ADDRESS:0x001d 9 | ;%define proj_e_clear_screen KERNEL_LOAD_ADDRESS:0x00b7 10 | ;%define proj_e_move_cursor KERNEL_LOAD_ADDRESS:0x00c6 11 | 12 | ;%define proj_e_get_user_input KERNEL_LOAD_ADDRESS:0x004f 13 | ;%define proj_e_compare_string KERNEL_LOAD_ADDRESS:0x00a0 14 | 15 | %endif 16 | -------------------------------------------------------------------------------- /doc/memory_map.md: -------------------------------------------------------------------------------- 1 | # General memory map 2 | 3 | Start | End | Size | Type | Description 4 | ------+-----+------+------+------------ 5 | 0x0000 | 0x03ff | 1 KiB | RAM | unusable, Real Move IVT (Interrupt Vector Table) 6 | 0x0400 | 0x04ff | 256 B | RAM | unusable, BDA (BIOS data area) 7 | 0x0500 | 0x7bff | 30'463 B | RAM | free for use 8 | 0x7c00 | 0x7dff | 512 B | RAM | unusable, OS Boot Sector 9 | 0x7e00 | 0x7ffff | 480.5 KiB | RAM | free for use 10 | 0x80000 | 0x9fbff | 120 KiB | RAM (**if it exists**) | free for use 11 | 0x9fc00 | 0x9ffff | 1 KiB | RAM | unusable, EBDA (Extended BIOS data area) 12 | 0xa0000 | 0xfffff | 384 KiB | various | unusable, video memory or ROM area 13 | 14 | # Project-E memory map 15 | Start | End | Size | Type | Description 16 | ------+-----+------+------+------------ 17 | 0x1000 | 0x4fff | 16 KiB | RAM | Kernel 18 | 0x7c00 | 0x7dff | 512 B | RAM | OS Boot Sector 19 | 0x7e00 | 0x7ffff | 480.5 KiB | User space (Applications and files will be loaded here) 20 | -------------------------------------------------------------------------------- /std/disk.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_disk_asm 2 | %define proj_e_disk_asm 3 | 4 | ; parameters 5 | sectors_per_track dw 18 6 | heads_per_track dw 2 7 | bytes_per_sector dw 512 8 | drive_number dw 0 9 | msg_error_reading_floppy db '[!] Error while reading floppy', 13, 10, 0 10 | msg_error_writing_floppy db '[!] Error while writing to floppy', 13, 10, 0 11 | ; variables 12 | abs_sector dw 0 13 | abs_head dw 0 14 | abs_track dw 0 15 | lba_number dw 0 16 | destination dw 0 17 | 18 | ; Routine to convert a LBA (Logical Block Addresing) to CHS (Cylinder/Head/Sector) 19 | ; INPUT : AX (LBA addr), sectors_per_track, heads_per_track 20 | ; OUTPUT : abs_sector (CHS sector addr), abs_head (CHS head addr), abs_track (CHS track addr) 21 | proj_e_lbachs: 22 | xor dx, dx 23 | div word [sectors_per_track] 24 | inc dl 25 | mov byte [abs_sector], dl 26 | 27 | xor dx, dx 28 | div word [heads_per_track] 29 | mov byte [abs_head], dl 30 | mov byte [abs_track], al 31 | 32 | ret 33 | 34 | %endif 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: image 2 | 3 | image: build bootloader kernel app plum 4 | @dd if=/dev/zero of=build/project_e.img bs=512 count=2880 5 | @dd status=noxfer conv=notrunc if=build/boot.bin of=build/project_e.img bs=512 seek=0 6 | @dd status=noxfer conv=notrunc if=build/kernel.bin of=build/project_e.img bs=512 seek=1 7 | @dd status=noxfer conv=notrunc if=build/app.bin of=build/project_e.img bs=512 seek=33 8 | @dd status=noxfer conv=notrunc if=build/plum.bin of=build/project_e.img bs=512 seek=41 9 | @echo project_e.img created 10 | 11 | build: 12 | @mkdir build 13 | 14 | bootloader: 15 | @nasm -O0 -f bin -o build/boot.bin src/boot.asm 16 | @echo Bootloader built 17 | 18 | kernel: 19 | @nasm -O0 -f bin -o build/kernel.bin src/kernel.asm 20 | @echo Kernel built 21 | 22 | app: 23 | @nasm -O0 -f bin -o build/app.bin src/app.asm 24 | @echo App built 25 | 26 | plum: 27 | @nasm -O0 -f bin -o build/plum.bin src/plum.asm 28 | @echo Plum built 29 | 30 | clean: 31 | @rm -f build/* 32 | @rm -f iso/* 33 | @echo Build and iso folders cleaned 34 | 35 | mrproper: clean 36 | @rm -rf build/ 37 | -------------------------------------------------------------------------------- /doc/plum.md: -------------------------------------------------------------------------------- 1 | # Plum 2 | 3 | *Specification of the language* 4 | 5 | Plum is a small esoteric language for Project-E, made just for fun. 6 | 7 | A Plum program can use a maximum of 255 characters, as well as a stack of 256 bytes. 8 | 9 | ## Specification 10 | 11 | `!` print the current memory until it finds a null character 12 | 13 | `?` ask the user for an input. Maximum of 256 characters. 14 | 15 | `b[x]` send the memory pointer back `x` times. 16 | 17 | `B` is a short-hand for `b01`. 18 | 19 | `f[x]` send the memory pointer foward `x` times. 20 | 21 | `F` is a short-hand for `f01`. 22 | 23 | `s[x]` store `x` in the memory and move the stack pointer forward. 24 | 25 | `S[...]` store all the following characters as bytes in memory, until it finds a `$`. 26 | 27 | `i[x]` if memory[ptr] != 0, set the code pointer to `x`. 28 | 29 | `P` prints the current memory pointer. 30 | 31 | `x` is a 2 hex digits number : `[0-9a-fA-F]{2}'` 32 | 33 | ## Examples 34 | 35 | hello world 36 | 37 | ``` 38 | Shello world$b0b! 39 | ``` 40 | 41 | a=1, b=2, c=a+b, print(c) 42 | 43 | ``` 44 | s01s02+s48+! 45 | ``` 46 | -------------------------------------------------------------------------------- /std/video.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_video_asm 2 | %define proj_e_video_asm 3 | 4 | bits 16 5 | 6 | %include "std/macros.asm" 7 | 8 | ; Routine to clear the screen 9 | ; INPUT : AH (color to use to clear the screen) 10 | ; OUTPUT : none 11 | proj_e_clear_screen: 12 | mov bx, VIDMEM 13 | mov es, bx 14 | mov al, ' ' 15 | mov cx, 2000 ; COLS*LINES 16 | xor di, di ; video offset=0 17 | rep stosw 18 | 19 | ret 20 | 21 | ; Routine to move the cursor on the screen 22 | ; INPUT : CH (y position), CL (x position) 23 | ; OUTPUT : none 24 | proj_e_move_cursor: 25 | mov dl, cl ; set position of cursor 26 | mov dh, ch 27 | 28 | mov ah, 0x02 ; linked with int 0x10 : move cursor 29 | xor bh, bh ; page number 30 | int 0x10 31 | 32 | ret 33 | 34 | ; avoid this, just testing some stuff 35 | proj_e_init_vid_mem: 36 | mov ax, VIDMEM 37 | mov es, ax ; Set video segment to VIDMEM 38 | mov ax, 0x4020 ; colour + space character(0x20) 39 | mov cx, 2000 ; Number of cells to update 80*25=2000 40 | xor di, di ; Video offset starts at 0 (upper left of screen) 41 | rep stosw ; Store AX to CX # of words starting at ES:[DI] 42 | 43 | ret 44 | 45 | %endif 46 | -------------------------------------------------------------------------------- /src/boot.asm: -------------------------------------------------------------------------------- 1 | bits 16 2 | org 0x7c00 3 | 4 | start: 5 | jmp main 6 | 7 | %include "std/stdio.asm" 8 | %include "std/readdisk.asm" 9 | 10 | data: 11 | ; strings 12 | title db 'Project E', 13, 10, '=========', 13, 10, 13, 10, 0 13 | msg_info db '[Bootloader] Started in 16 bits (real mode)', 13, 10, 0 14 | msg_kernel_loaded db '[Bootloader] Kernel loaded into RAM @ 0x1000', 13, 10, 0 15 | msg_kernel_load_err db '[!] [Bootloader] Could not load kernel', 13, 10, 0 16 | ; parameters 17 | KERNEL_BLOCK_START equ 1 18 | KERNEL_BLOCKS_SIZE equ 32 19 | KERNEL_SEGMENT equ 0x0100 ; 0x0100:0x0000=0x1000 20 | 21 | main: 22 | cli 23 | xor ax, ax 24 | mov ds, ax 25 | mov es, ax 26 | 27 | ; init the stack 28 | mov ss, ax 29 | mov sp, 0x7c00 30 | sti 31 | 32 | ; display message on startup 33 | print title 34 | print msg_info 35 | 36 | ; loading kernel 37 | load_file KERNEL_BLOCK_START, KERNEL_SEGMENT, KERNEL_BLOCKS_SIZE 38 | 39 | jnc .jump_to_kernel 40 | 41 | .kernel_loading_error: 42 | print msg_kernel_load_err 43 | cli 44 | hlt 45 | 46 | .jump_to_kernel: 47 | print msg_kernel_loaded 48 | jmp KERNEL_SEGMENT:0x0000 49 | 50 | ; pad to 510 bytes (boot sector - 2) 51 | times 510-($-$$) db 0 52 | ; standard boot signature 53 | dw 0xAA55 54 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ge "1" ] && [ "$1" == "qemu" ]; then 4 | echo Running qemu 5 | debug_options="-d int,cpu_reset,guest_errors -no-reboot -no-shutdown" 6 | command="qemu-system-i386 -drive format=raw,if=floppy,index=0,file=build/project_e.img -m 128 -s " 7 | if [ "$#" -eq "2" ] && [ "$2" == "debug" ]; then 8 | command=$command$debug_options 9 | fi 10 | $command || exit 0 11 | else 12 | echo Building ISO 13 | if [ ! -d "iso" ]; then 14 | mkdir iso 15 | else 16 | # clean working directory 17 | rm -rf iso/ 18 | mkdir iso 19 | fi 20 | 21 | dd if=/dev/zero of=iso/floppy.img bs=512 count=2880 # creating a 512*2880 disk (size of a 1.44MB floppy) 22 | dd if=build/project_e.img of=iso/floppy.img seek=0 bs=512 count=87 conv=notrunc # we are copying 87 sectors 23 | # 1 for the Bootloader 24 | # 32 for the Kernel 25 | # 64 for the 8 apps 26 | 27 | #cd iso 28 | #genisoimage -quiet -V "Project-E" -input-charset iso8859-1 -c boot.cat -l -R -J \ 29 | # -boot-info-table -no-emul-boot -boot-load-size 4 \ 30 | # -o project_e.iso -b floppy.img -hide floppy.img ./ 31 | exit 0 32 | fi 33 | 34 | # if we are here we probably missed a "if" 35 | echo "No argument : build the ISO in cdiso/" 36 | echo "1st argument : qemu => after having built the floppy, run qemu" 37 | echo "2nd argument : debug => to run qemu with debug options" 38 | exit 1 39 | -------------------------------------------------------------------------------- /std/macros.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_macros_asm 2 | %define proj_e_macros_asm 3 | 4 | ; -------------------------- 5 | ; Strings 6 | ; -------------------------- 7 | 8 | %macro print 1 9 | push si 10 | mov si, %1 11 | call proj_e_print 12 | pop si 13 | %endmacro 14 | 15 | %macro printhex 1 16 | mov ax, %1 17 | call proj_e_print_hex 18 | %endmacro 19 | 20 | %macro mystosb 1 21 | mov [%1], al 22 | inc %1 23 | %endmacro 24 | 25 | %macro input 2 26 | mov di, %1 27 | mov ch, %2 28 | mov bl, 0x00 29 | call proj_e_get_user_input 30 | %endmacro 31 | 32 | %macro getpass 2 33 | mov di, %1 34 | mov ch, %2 35 | mov bl, 0x01 36 | call proj_e_get_user_input 37 | %endmacro 38 | 39 | %macro strcmp 2 40 | mov di, %1 41 | mov si, %2 42 | call proj_e_compare_string 43 | %endmacro 44 | 45 | ; -------------------------- 46 | ; Video Mode 47 | ; -------------------------- 48 | 49 | %define VIDMEM 0xb800 ; video memory 50 | %define COLS 80 ; width and height of screen 51 | %define LINES 25 52 | 53 | %define CHAR_ATTR_BLACK 0 54 | %define CHAR_ATTR_BLUE 1 55 | %define CHAR_ATTR_GREEN 2 56 | %define CHAR_ATTR_CYAN 3 57 | %define CHAR_ATTR_RED 4 58 | %define CHAR_ATTR_MAGENT 5 59 | %define CHAR_ATTR_BROWN 6 60 | %define CHAR_ATTR_LGREY 7 61 | %define CHAR_ATTR_DGREY 8 62 | %define CHAR_ATTR_LBLUE 9 63 | %define CHAR_ATTR_LGREEN 10 64 | %define CHAR_ATTR_LCYAN 11 65 | %define CHAR_ATTR_LRED 12 66 | %define CHAR_ATTR_LMAGENT 13 67 | %define CHAR_ATTR_YELLOW 14 68 | %define CHAR_ATTR_WHITE 15 69 | 70 | %define CREATE_COLOUR(fg, bg) ((fg) + (bg) * 16) 71 | 72 | %macro move_cursor 1 73 | mov cx, %1 74 | call proj_e_move_cursor 75 | %endmacro 76 | 77 | %endif 78 | -------------------------------------------------------------------------------- /src/app.asm: -------------------------------------------------------------------------------- 1 | ; in which mode our application will be running 2 | bits 16 3 | 4 | ; entry point of our application 5 | start: 6 | jmp main 7 | 8 | ; all the includes we must do will be right there 9 | %include "std/macros.asm" 10 | %include "std/defines.asm" 11 | ; needed for print(), to change the colors of the screen, clear it, 12 | ; and move the cursor, input() and strcmp() 13 | %include "std/string.asm" 14 | %include "std/stdio.asm" 15 | %include "std/video.asm" 16 | 17 | ; our variables will be right there 18 | data: 19 | message db 'Hello world ! This a string terminated by \r\n', 13, 10, 0 20 | prompt db '> ', 0 21 | new_line db 13, 10, 0 22 | 23 | ; MAXSIZE_BUFFER is the maximum size of our buffer 24 | MAXSIZE_BUFFER equ 72 25 | input_buffer times MAXSIZE_BUFFER db 0 26 | 27 | HELLO db 'hello', 0 28 | 29 | ; the code of our application will be right there 30 | main: 31 | mov ax, cs 32 | mov ds, ax 33 | 34 | ; an handy macro print out a given message 35 | print message 36 | ; to get an input from the user, we must give both the buffer and its maximum size 37 | print prompt 38 | input input_buffer, MAXSIZE_BUFFER 39 | strcmp input_buffer, HELLO 40 | jc .end ; jc means "jump carry". we want to jump if the carry flag is on 41 | ; (activated if the two strings are identical) 42 | ; to the end of our program, to avoid printing the input_buffer 43 | 44 | ; echo back what the user wrote if it isn't "hello" 45 | print input_buffer 46 | print new_line 47 | 48 | .end: 49 | ; nothing more to do, we give back the control to the kernel ! 50 | jmp KERNEL_LOAD_ADDRESS:0x0000 51 | 52 | ; to ensure our application fits in the 4kB 53 | times 4096-($-$$) db 0 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project E 2 | 3 | Project E is a small Operating System (if we can say so) composed of a basic bootloader and of a simple kernel, capable of execute shell commands. 4 | 5 | It was developped and tested under Linux Mint 19, on a x86_64 machine. 6 | 7 | ## Building 8 | 9 | ```bash 10 | ~$ cd project-E-master 11 | ~/project-E-master$ sudo ./configure.sh # to install the dependencies 12 | ~/project-E-master$ make && ./run.sh qemu 13 | ``` 14 | 15 | ## Burning on an USB key 16 | 17 | ```bash 18 | ~$ cd project-E-master 19 | ~/project-E-master$ make && ./runs.sh # make the binary image and build the ISO 20 | ~/project-E-master$ sudo fdisk -l 21 | # ... 22 | # find the USB you want to burn the ISO to 23 | # WARNING : it will completly wipe the data on this USB disk ! 24 | # You have been warned, IN NO EVENT SHALL THE 25 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | # SOFTWARE. (see MIT License) 29 | ~/project-E-master$ ./deploy.sh /dev/sdX # here my disk is /dev/sdX 30 | # replace by yours before executing the command 31 | ~/project-E-master$ sudo qemu-system-i386 -cdrom /dev/sdX # testing the installation 32 | ``` 33 | 34 | ## Making your own application to use in Project E 35 | 36 | We have a nice application template under src/app.asm. 37 | 38 | ## Bug report 39 | 40 | Feel free to open an issue if you encounter any kind of problem with Project E :smiley: 41 | 42 | ## Updates 43 | 44 | **v0.3.0** Plum was debugged : now all the operator behave as wanted ! 45 | 46 | **v0.2.4** Enhancing plum : read line, operators, going backward/forward, adding an if 47 | 48 | **v0.2.1** Password update: adding a `lock` command to lock the kernel 49 | 50 | **v0.2.0** Commands update: adding `echo`, `color`, `clear`, `version` 51 | 52 | **v0.1.1** Adding a new program: Plum (small interpreter for an esoteric language) 53 | 54 | **v0.1.0** First version: last state was in commit n°42. Took only 5 days to be developped 55 | -------------------------------------------------------------------------------- /std/writedisk.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_writedisk_asm 2 | %define proj_e_writedisk_asm 3 | 4 | bits 16 5 | 6 | ; Macro to write files more easily 7 | ; INPUT : AX (LBA number for sector), BX (linear address, where the file is loaded), CX (sectors count) 8 | ; OUTPUT : CF if an error happen while to find it, ES:BX (where it's loaded) 9 | %macro write_file 3 10 | mov ax, %1 11 | mov bx, %2 12 | mov cx, %3 13 | call proj_e_write_file 14 | %endmacro 15 | 16 | %include "std/disk.asm" 17 | 18 | ; Routine to write files into memory more easily 19 | ; INPUT : AX (LBA number for sector), BX (linear address, where the file is loaded), CX (sectors count) 20 | ; OUTPUT : CF if an error happen while to find it, ES:BX (where it's loaded) 21 | proj_e_write_file: 22 | .begin: 23 | ; save destination 24 | mov word [destination], bx 25 | ; save LBA 26 | mov word [lba_number], ax 27 | .main: 28 | mov di, 0x0005 ; retry count 29 | .loop: 30 | ; get track, sector, and head 31 | mov ax, word [lba_number] 32 | call proj_e_lbachs 33 | 34 | ; move forward to avoid overwriting 35 | push word [destination] 36 | pop es 37 | xor bx, bx 38 | 39 | ; save counter (for loop) 40 | push cx 41 | 42 | ; write to floppy 43 | mov ah, 0x03 44 | mov al, 0x01 45 | mov ch, byte [abs_track] 46 | mov cl, byte [abs_sector] 47 | mov dh, byte [abs_head] 48 | mov dl, byte [drive_number] 49 | int 0x13 50 | 51 | ; restore counter 52 | pop cx 53 | 54 | ; if no error, go to next sector 55 | jnc .sectordone 56 | 57 | ; otherwise reset disk 58 | xor ax, ax 59 | int 0x13 60 | ; decrement number of trials left 61 | dec di 62 | 63 | ; retry 64 | jnz .loop 65 | ; we got an error, impossible to write. 66 | jmp .error 67 | .sectordone: 68 | ; move forward 69 | add word [destination], 0x20 70 | ; increment LBA 71 | inc word [lba_number] 72 | ; decrement ecx, go to label while non-zero 73 | loop .main 74 | jmp .quit 75 | .error: 76 | ; set carry flag 77 | stc 78 | ; optionnal but well it's handy 79 | print msg_error_writing_floppy 80 | ; cleaning up things 81 | jmp .end 82 | .quit: 83 | ; if we "quit" it means there were no errors, so clear the carry flag 84 | clc 85 | .end: 86 | ret 87 | 88 | %endif 89 | -------------------------------------------------------------------------------- /std/readdisk.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_readdisk_asm 2 | %define proj_e_readdisk_asm 3 | 4 | bits 16 5 | 6 | ; Macro to read file more easily 7 | ; INPUT : AX (LBA number for sector), BX (linear address, where it will be loaded), CX (sectors count) 8 | ; OUTPUT : CF if an error happen while to find it, ES:BX (where it's loaded) 9 | %macro load_file 3 10 | mov ax, %1 11 | mov bx, %2 12 | mov cx, %3 13 | call proj_e_read_file 14 | %endmacro 15 | 16 | %include "std/disk.asm" 17 | %include "std/macros.asm" 18 | 19 | ; Routine to read files into memory more easily 20 | ; INPUT : AX (LBA number for sector), BX (linear address, where it will be loaded), CX (sectors count) 21 | ; OUTPUT : CF if an error happen while to find it, ES:BX (where it's loaded) 22 | proj_e_read_file: 23 | .begin: 24 | ; save destination 25 | mov word [destination], bx 26 | ; save LBA 27 | mov word [lba_number], ax 28 | .main: 29 | mov di, 0x0005 ; retry count 30 | .loop: 31 | ; get track, sector, and head 32 | mov ax, word [lba_number] 33 | call proj_e_lbachs 34 | 35 | ; move forward to avoid overwriting 36 | push word [destination] 37 | pop es 38 | xor bx, bx 39 | 40 | ; save counter (for loop) 41 | push cx 42 | 43 | ; read floppy 44 | mov ah, 0x02 45 | mov al, 0x01 46 | mov ch, byte [abs_track] 47 | mov cl, byte [abs_sector] 48 | mov dh, byte [abs_head] 49 | mov dl, byte [drive_number] 50 | int 0x13 51 | 52 | ; restore counter 53 | pop cx 54 | 55 | ; if no error, go to next sector 56 | jnc .sectordone 57 | 58 | ; otherwise reset disk 59 | xor ax, ax 60 | int 0x13 61 | ; decrement number of trials left 62 | dec di 63 | 64 | ; retry 65 | jnz .loop 66 | ; we got an error, impossible to read. 67 | jmp .error 68 | .sectordone: 69 | ; move forward 70 | add word [destination], 0x20 71 | ; increment LBA 72 | inc word [lba_number] 73 | ; decrement ecx, go to label while non-zero 74 | loop .main 75 | jmp .quit 76 | .error: 77 | ; set carry flag 78 | stc 79 | ; optionnal but well it's handy 80 | print msg_error_reading_floppy 81 | ; cleaning up things 82 | jmp .end 83 | .quit: 84 | ; if we "quit" it means there were no errors, so clear the carry flag 85 | clc 86 | .end: 87 | ret 88 | 89 | %endif 90 | -------------------------------------------------------------------------------- /std/stdio.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_stdio_asm 2 | %define proj_e_stdio_asm 3 | 4 | bits 16 5 | 6 | %include "std/macros.asm" 7 | 8 | nl db 13, 10, 0 9 | 10 | ; Routine for outputting a string on the screen 11 | ; INPUT : SI (containing address of the string) 12 | ; OUTPUT : none 13 | proj_e_print: 14 | mov ah, 0x0e ; specify int 0x10 (teletype output) 15 | .printchar: 16 | lodsb ; load byte from si into al, increment si 17 | cmp al, 0 ; is it the end of the string ? 18 | je .done ; yes => quit ; no => continue 19 | int 0x10 ; print the character 20 | jmp .printchar 21 | .done: 22 | ret 23 | 24 | ; Routine to reboot the machine 25 | ; INPUT : none 26 | ; OUTPUT : none 27 | proj_e_reboot: 28 | db 0x0ea ; sending us to the end of the memory, to reboot 29 | dw 0x0000 30 | dw 0xffff 31 | 32 | ; Routine to wait for any key press 33 | ; INPUT : none 34 | ; OUTPUT : none 35 | proj_e_waitkeypress: 36 | mov ah, 0 37 | int 0x16 ; BIOS keyboard service 38 | ret 39 | 40 | ; Routine to print an hexadecimal word to screen 41 | ; INPUT : AX (number) 42 | ; OUTPUT : none 43 | proj_e_print_hex: 44 | push ax 45 | call proj_e_print_hex_word 46 | ; cleanup stack after call 47 | add sp, 2 48 | ret 49 | 50 | ; Routine to print an hexadecimal word to screen 51 | ; INPUT : number on the stack 52 | ; OUTPUT : none 53 | proj_e_print_hex_word: 54 | push bp 55 | mov bp, sp ; BP=SP, on 8086 can't use sp in memory operand 56 | push dx ; Save all registers we clobber 57 | push cx 58 | push bx 59 | push ax 60 | 61 | mov cx, 0x0404 ; CH = number of nibbles to process = 4 (4*4=16 bits) 62 | ; CL = Number of bits to rotate each iteration = 4 (a nibble) 63 | mov dx, [bp+4] ; DX = word parameter on stack at [bp+4] to print 64 | mov bx, [bp+6] ; BX = page / foreground attr is at [bp+6] 65 | 66 | .loop: 67 | rol dx, cl ; Roll 4 bits left. Lower nibble is value to print 68 | mov ax, 0x0e0f ; AH=0E (BIOS tty print),AL=mask to get lower nibble 69 | and al, dl ; AL=copy of lower nibble 70 | add al, 0x90 ; Work as if we are packed BCD 71 | daa ; Decimal adjust after add. 72 | ; If nibble in AL was between 0 and 9, then CF=0 and 73 | ; AL=0x90 to 0x99 74 | ; If nibble in AL was between A and F, then CF=1 and 75 | ; AL=0x00 to 0x05 76 | adc al, 0x40 ; AL=0xD0 to 0xD9 77 | ; or AL=0x41 to 0x46 78 | daa ; AL=0x30 to 0x39 (ASCII '0' to '9') 79 | ; or AL=0x41 to 0x46 (ASCII 'A' to 'F') 80 | int 0x10 ; Print ASCII character in AL 81 | dec ch 82 | jnz .loop ; Go back if more nibbles to process 83 | 84 | pop ax ; Restore registers 85 | pop bx 86 | pop cx 87 | pop dx 88 | pop bp 89 | ret 90 | 91 | %endif 92 | -------------------------------------------------------------------------------- /src/kernel.asm: -------------------------------------------------------------------------------- 1 | bits 16 2 | org 0 3 | [map all kernel.map] 4 | 5 | start: 6 | jmp main 7 | 8 | %macro check_command 3 9 | mov di, %1 10 | call %2 11 | jc %3 12 | mov si, buffer 13 | %endmacro 14 | 15 | %include "std/stdio.asm" 16 | %include "std/string.asm" 17 | %include "std/video.asm" 18 | %include "std/readdisk.asm" 19 | 20 | data: 21 | version db 'v0.3.0', 0 22 | msg_info db 'Project-E is a monolithic kernel working in 16-bits real mode', 13, 10 23 | db 'developped by SuperFola', 13, 10 24 | db 0 25 | msg_app_load_ok db '[Kernel] App loaded into RAM @ 0x8000', 13, 10, 0 26 | msg_app_load_err db '[!] [Kernel] Could not load app', 13, 10, 0 27 | msg_plum_load_ok db '[Kernel] Plum loaded into RAM @ 0x9000', 13, 10, 0 28 | msg_plum_load_err db '[!] [Kernel] Could not load plum', 13, 10, 0 29 | msg_error_clear db '[!] [Clear] Wrong argument', 13, 10, 0 30 | 31 | buffer times 72 db 0 32 | password db 115, 99, 105, 112, 105, 111, 0 33 | 34 | ask_pass db 'Password? ', 0 35 | wrong_pass db '[!] Wrong password', 13, 10, 0 36 | good_pass db '[Info] Access granted', 13, 10, 0 37 | pwd_state db 0 38 | 39 | pwd_lock times 72 db 0 40 | ask_lock db 'Lock? ', 0 41 | 42 | cursor db 'kernel> ', 0 43 | command_echo db 'echo', 0 44 | command_clear db 'clear', 0 45 | command_help db 'help', 0 46 | command_rbt db 'reboot', 0 47 | command_info db 'info', 0 48 | command_test db 'test', 0 49 | command_plum db 'plum', 0 50 | command_ver db 'version', 0 51 | command_lock db 'lock', 0 52 | action_help db 'echo clear help reboot info test plum', 13, 10 53 | db 'version lock', 13, 10 54 | db 0 55 | 56 | msg_cmd_error db 'Unknown command', 13, 10, 0 57 | 58 | APP_BLOCK_START equ 33 59 | APP_BLOCKS_SIZE equ 8 ; 8*512B=4096B 60 | APP_SEGMENT equ 0x07e0 ; 0x07e0:0x0000=0x7e00 61 | 62 | PLUM_BLOCK_START equ 41 63 | PLUM_BLOCKS_SIZE equ 32 ; 32*512B=16384B 64 | PLUM_SEGMENT equ 0x0900 ; 0x0900:0x0000=0x9000 65 | 66 | main: 67 | ; set up registers 68 | mov ax, cs 69 | mov ds, ax 70 | 71 | print nl 72 | 73 | ; retry counter for password 74 | mov cx, 3 75 | ; if has_laready_entered_pwd_and_correct 76 | cmp byte [pwd_state], 1 77 | ; go to shell 78 | je shell_begin 79 | ; otherwise, ask for it 80 | 81 | .ask: 82 | push cx 83 | print ask_pass 84 | 85 | ; get password 86 | getpass buffer, 72 87 | mov si, buffer 88 | 89 | ; compare strings 90 | mov di, password 91 | call proj_e_compare_string 92 | jc .access_granted 93 | print wrong_pass 94 | print nl 95 | 96 | ; decrement retry counter, when equal to 0, infinite loop 97 | pop cx 98 | sub cx, 1 99 | cmp cx, 0 100 | jne .ask 101 | 102 | mov byte [pwd_state], 255 103 | cli 104 | hlt 105 | 106 | .access_granted: 107 | mov byte [pwd_state], 1 108 | print good_pass 109 | print nl 110 | 111 | shell_begin: 112 | ; just in case 113 | clc 114 | 115 | print cursor 116 | ; ask for user input 117 | input buffer, 72 118 | ; to be able to do the comparisons tests 119 | mov si, buffer 120 | 121 | ; clear XY 122 | check_command command_clear, proj_e_compare_start_string, .clear 123 | ; echo ... 124 | check_command command_echo, proj_e_compare_start_string, .echo 125 | ; help 126 | check_command command_help, proj_e_compare_string, .help 127 | ; reboot 128 | check_command command_rbt, proj_e_compare_string, .reboot 129 | ; info 130 | check_command command_info, proj_e_compare_string, .info 131 | ; test: load app 132 | check_command command_test, proj_e_compare_string, .test 133 | ; plum: load app 134 | check_command command_plum, proj_e_compare_string, .plum 135 | ; version 136 | check_command command_ver, proj_e_compare_string, .version 137 | ; lock 138 | check_command command_lock, proj_e_compare_string, .lock 139 | 140 | ; wrong user input (command not recognized) 141 | ; only print message if there was a command 142 | .wrong_input_error: 143 | ; if len(str) == 0: 144 | call proj_e_length_string 145 | cmp ax, 0 146 | ; then 147 | ; don't print message 148 | je shell_begin 149 | ; else: 150 | ; error, can not find command 151 | print msg_cmd_error 152 | jmp shell_begin 153 | 154 | .echo: 155 | ; if str == "echo": 156 | mov si, buffer 157 | mov di, command_echo 158 | call proj_e_compare_string 159 | ; then 160 | ; print only a newline 161 | jc .nl_echo 162 | ; else: 163 | ; wipe "echo " 164 | call proj_e_tokenize_string 165 | mov si, di 166 | ; check arguments length 167 | call proj_e_length_string 168 | cmp ax, 0 169 | ; if no argument, print a newline 170 | je .nl_echo 171 | ; else, print arguments 172 | print si 173 | print nl 174 | jmp .end_echo 175 | 176 | .nl_echo: 177 | print nl 178 | 179 | .end_echo: 180 | jmp shell_begin 181 | 182 | .clear: 183 | ; if len(cmd.split(' ')) != 2: 184 | call proj_e_tokenize_string 185 | mov si, di 186 | call proj_e_length_string 187 | cmp ax, 2 188 | ; then 189 | ; error() 190 | jne .error_clear 191 | ; else: 192 | ; try: convertToInteger(cmd.split(' ')[1]) 193 | call proj_e_2hex_to_int 194 | ; except: error() 195 | jc .error_clear 196 | ; finally: 197 | mov ah, al ; parsed integer is in AL, not in AH 198 | ; clear screen 199 | call proj_e_clear_screen 200 | move_cursor 0x0000 ; move cursor in x=0,y=0 201 | jmp .end_clear 202 | 203 | .error_clear: 204 | print msg_error_clear 205 | 206 | .end_clear: 207 | jmp shell_begin 208 | 209 | .help: 210 | print action_help 211 | jmp shell_begin 212 | 213 | .reboot: 214 | call proj_e_reboot 215 | 216 | .info: 217 | print msg_info 218 | jmp shell_begin 219 | 220 | .test: 221 | mov ah, CREATE_COLOUR(CHAR_ATTR_CYAN, CHAR_ATTR_RED) ; cyan on red background 222 | call proj_e_clear_screen 223 | ; move cursor in x=0,y=0 224 | move_cursor 0x0000 225 | 226 | ; prepare to load app 227 | load_file APP_BLOCK_START, APP_SEGMENT, APP_BLOCKS_SIZE 228 | jnc .jump_to_app ; loading success, no error in carry flag 229 | 230 | .app_loading_error: 231 | print msg_app_load_err 232 | jmp shell_begin 233 | 234 | .jump_to_app: 235 | print msg_app_load_ok 236 | jmp APP_SEGMENT:0x0000 237 | 238 | .plum: 239 | mov ah, CREATE_COLOUR(CHAR_ATTR_WHITE, CHAR_ATTR_BLACK) 240 | call proj_e_clear_screen 241 | ; move cursor in x=0, y=0 242 | move_cursor 0x0000 243 | 244 | ; prepare to load app 245 | load_file PLUM_BLOCK_START, PLUM_SEGMENT, PLUM_BLOCKS_SIZE 246 | jnc .jump_to_plum 247 | 248 | .plum_loading_error: 249 | print msg_plum_load_err 250 | jmp shell_begin 251 | 252 | .jump_to_plum: 253 | print msg_plum_load_ok 254 | jmp PLUM_SEGMENT:0x0000 255 | 256 | .version: 257 | print version 258 | print nl 259 | jmp shell_begin 260 | 261 | .lock: 262 | ; get password 263 | print ask_lock 264 | getpass pwd_lock, 72 265 | 266 | ; while (1) 267 | .loop_pwd: 268 | ; buffer=getpass("Password? ") 269 | print ask_pass 270 | getpass buffer, 72 271 | ; if buffer != ask_lock: 272 | mov si, buffer 273 | mov di, pwd_lock 274 | call proj_e_compare_string 275 | ; then 276 | ; loop again 277 | jnc .loop_pwd 278 | ; else: 279 | ; go to kernel 280 | jmp shell_begin 281 | 282 | ; 16kB kernel 283 | times 16384-($-$$) db 0 284 | -------------------------------------------------------------------------------- /std/string.asm: -------------------------------------------------------------------------------- 1 | %ifndef proj_e_string_asm 2 | %define proj_e_string_asm 3 | 4 | bits 16 5 | 6 | ; Routine to get a string from the user 7 | ; Waits for a complete string of user input and puts it in buffer 8 | ; Sensitive for backspace and Enter buttons 9 | ; INPUT : DI (buffer address), CH (characters count limit), BL (a value != 0 will print * instead of the character) 10 | ; OUTPUT : input in buffer 11 | proj_e_get_user_input: 12 | cld ; clearing direction flag 13 | xor cl, cl ; CL will be our counter to keep track of the number of characters the user has entered. 14 | ; XORing cl by itself will set it to zero. 15 | .get_char_and_add_to_buffer: 16 | xor ah, ah ; We use bios interrupt 0x16 to capture user input. 17 | ; AH=0 is an option for 0x16 that tells the interrupt to read the user input character 18 | int 0x16 ; call interrupt. Stores read character in AL 19 | 20 | ; backspace button listener 21 | cmp al, 0x08 ; compares user input to the backspace button, stores result in Carry Flag 22 | je .backspace_pressed ; if the results of the compare is 1, go to subroutine .backspace_pressed 23 | 24 | ; enter button listener 25 | cmp al, 0x0d ; compares user input to enter button 26 | je .enter_pressed ; go to appropriate subroutine for enter button 27 | 28 | ; input counter 29 | cmp cl, ch ; Has the user entered 'ch' bytes yet? (buffer limit is in register ch) 30 | je .buffer_overflow 31 | 32 | ; User input is normal character 33 | cmp bl, 0 34 | je .put_normal_char 35 | 36 | ; save al for later 37 | push ax 38 | mov al, '*' ; if bl != 0, then replace the character with a '*' 39 | mov ah, 0x0e ; Teletype mode 40 | int 0x10 ; Print interrupt 41 | pop ax 42 | 43 | jmp .next 44 | 45 | .put_normal_char: 46 | mov ah, 0x0e ; Teletype mode 47 | int 0x10 ; Print interrupt 48 | 49 | .next: 50 | ; puts character in buffer 51 | mystosb di 52 | 53 | inc cl ; increment counter 54 | jmp .get_char_and_add_to_buffer ; recurse 55 | 56 | .backspace_pressed: 57 | cmp cl, 0 ; no point erasing anything if no input has been entered 58 | je .get_char_and_add_to_buffer ; ignore backspace press 59 | 60 | ; Delete last input character from buffer 61 | ; When you use stosb, movsb or similar functions, the system implicitly uses the SI and DI registers. 62 | dec di ; Therefore we need to decrement di to get to the last input character and erase it. 63 | mov byte [di], 0 ; Erases the byte at location [di] 64 | dec cl ; decrement our counter 65 | 66 | ; Erase character from display 67 | mov ah, 0x0e ; Teletype mode again 68 | mov al, 0x08 ; Backspace character 69 | int 0x10 70 | 71 | mov al, ' ' ; Empty character to print 72 | int 0x10 73 | 74 | mov al, 0x08 75 | int 0x10 76 | 77 | jmp .get_char_and_add_to_buffer ; go back to main routine 78 | 79 | ; enter button pressed. Jump to exit 80 | .enter_pressed: 81 | jmp .exit_routine 82 | 83 | ; buffer overflow (buffer is full). Don't accept any more chars and exit routine. 84 | .buffer_overflow: 85 | jmp .exit_routine 86 | 87 | .exit_routine: 88 | mov al, 0 ; end of user input signal 89 | mystosb di 90 | 91 | mov ah, 0x0e 92 | mov al, 0x0d ; new line 93 | int 0x10 94 | mov al, 0x0a 95 | int 0x10 96 | 97 | ret ; exit entire routine 98 | 99 | ; Routine to compare equality of two strings 100 | ; INPUT : SI (first string address), DI (second string address) 101 | ; OUTPUT : carry flag on if strings are equal 102 | ; N.B. : strings in SI and DI remain untouched 103 | proj_e_compare_string: 104 | .compare_next_character: 105 | ; a loop that goes character by character 106 | mov al, [si] ; focus on next byte in si 107 | mov bl, [di] ; focus on next byte in di 108 | cmp al, bl 109 | jne .conclude_not_equal ; if not equal, conclude and exit 110 | 111 | ; we know: two bytes are equal 112 | cmp al, 0 ; did we just compare two zeros? 113 | je .conclude_equal ; if yes, we've reached the end of the strings. They are equal. 114 | 115 | ; increment counters for next loop 116 | inc si 117 | inc di 118 | jmp .compare_next_character 119 | .conclude_equal: 120 | ; sets the carry flag (meaning that they ARE equal) 121 | stc 122 | jmp .done 123 | .conclude_not_equal: 124 | ; clears the carry flag (meaning that they ARE NOT equal) 125 | clc 126 | .done: 127 | ret 128 | 129 | ; Routine to compare two strings, the second one MUST match the beginning of the first one 130 | ; INPUT : SI (first string), DI (second string) 131 | ; OUTPUT : carry flag if strings are "equal" 132 | ; N.B. : strings in SI and DI remain untouched 133 | proj_e_compare_start_string: 134 | .compare_next_character: 135 | ; a loop that goes character by character 136 | mov al, [si] ; focus on next byte in si 137 | mov bl, [di] ; focus on next byte in di 138 | 139 | cmp bl, 0 140 | je .conclude_equal 141 | 142 | cmp al, bl 143 | jne .conclude_not_equal 144 | 145 | ; increment counters for next loop 146 | inc si 147 | inc di 148 | jmp .compare_next_character 149 | .conclude_equal: 150 | ; sets the carry flag (meaning that they ARE equal) 151 | stc 152 | jmp .done 153 | .conclude_not_equal: 154 | ; clears the carry flag (meaning that they ARE NOT equal) 155 | clc 156 | .done: 157 | ret 158 | 159 | ; Routine to get the size of a string 160 | ; INPUT : SI (string to get size of) 161 | ; OUTPUT : AX (length) 162 | proj_e_length_string: 163 | pusha 164 | mov cx, 0 165 | 166 | .more: 167 | cmp byte [si], 0 168 | je .done 169 | 170 | inc si 171 | inc cx 172 | jmp .more 173 | 174 | .done: 175 | mov word [.counter], cx 176 | popa 177 | mov ax, word [.counter] 178 | ret 179 | 180 | .counter dw 0 181 | 182 | ; Routine to tokenize a string "a b c d" (separator is given) 183 | ; INPUT : SI (string to split), AL (single char separator) 184 | ; OUTPUT : DI (next token or 0 if none) 185 | proj_e_tokenize_string: 186 | push si 187 | 188 | .next_char: 189 | ; do we have a matching separator ? 190 | cmp byte [si], al 191 | je .return_token 192 | ; is this the end of the string ? 193 | cmp byte [si], 0 194 | jz .no_more 195 | 196 | ; move forward 197 | inc si 198 | jmp .next_char 199 | 200 | .return_token: 201 | mov byte [si], 0 202 | inc si 203 | mov di, si 204 | pop si 205 | ret 206 | 207 | .no_more: 208 | mov di, 0 209 | pop si 210 | ret 211 | 212 | ; Routine to convert a single hexdigit string to integer 213 | ; INPUT : SI (string) 214 | ; OUTPUT : AL (number) 215 | proj_e_hex_to_int: 216 | ; '0': 48, '1': 49, '2': 50, '3': 51, '4': 52, '5': 53, '6': 54, '7': 55, '8': 56, '9': 57 217 | ; 'a': 97, 'b': 98, 'c': 99, 'd': 100, 'e': 101, 'f': 102 218 | ; 'A': 65, 'B': 66, 'C': 67, 'D': 68, 'E': 69, 'F': 70 219 | 220 | ; get first char 221 | mov byte al, [si] 222 | ; if 48 <= al <= 57: 223 | cmp al, 48 224 | jl .error 225 | cmp al, 57 226 | jg .try_upper_letter 227 | ; then 228 | ; convert from ASCII to real number 229 | sub al, 48 230 | mov byte [.val], al 231 | jmp .end 232 | 233 | .try_upper_letter: 234 | ; if 65 <= al <= 70 235 | cmp al, 65 236 | jl .error 237 | cmp al, 70 238 | jg .try_lower_letters 239 | ; then 240 | ; convert from ASCII to real number (+10, 'A' is the reference and 0xA == 10) 241 | sub al, 55 242 | mov byte [.val], al 243 | jmp .end 244 | 245 | .try_lower_letters: 246 | ; if 97 <= al <= 102: 247 | cmp al, 97 248 | jl .error 249 | cmp al, 102 250 | jg .error 251 | ; then 252 | ; convert from ASCII to real number (+10, 'a' is the reference and 0xa == 10) 253 | sub al, 87 254 | mov byte [.val], al 255 | jmp .end 256 | 257 | .end: 258 | clc 259 | jmp .done 260 | 261 | .error: 262 | stc 263 | print .error_msg 264 | 265 | .done: 266 | mov byte al, byte [.val] 267 | ret 268 | 269 | .val db 0 270 | .error_msg db 'Could not convert hex-digit string to integer', 13, 10, 0 271 | 272 | ; Routine to convert a 2 hexdigits string to integer 273 | ; INPUT : SI (string) 274 | ; OUTPUT : AL (number) 275 | proj_e_2hex_to_int: 276 | ; if len(str) != 2: 277 | call proj_e_length_string 278 | cmp ax, 2 279 | ; then 280 | ; error 281 | jne .error 282 | ; else: 283 | ; go to end of string 284 | add si, ax 285 | dec si 286 | 287 | ; convert last digit from the right 288 | call proj_e_hex_to_int 289 | jc .error 290 | mov byte [.val], byte al 291 | dec si 292 | 293 | ; convert the 2nd digit and multiply it by 16 294 | call proj_e_hex_to_int 295 | jc .error 296 | mov byte ah, 0x00 297 | shl ax, 0x04 298 | add byte [.val], byte al 299 | 300 | .end: 301 | clc 302 | jmp .done 303 | 304 | .error: 305 | stc 306 | print .error_msg 307 | 308 | .done: 309 | mov byte al, byte [.val] 310 | ret 311 | 312 | .val db 0 313 | .error_msg db 'Could not convert 2hex-digits string to integer', 13, 10, 0 314 | 315 | %endif 316 | -------------------------------------------------------------------------------- /src/plum.asm: -------------------------------------------------------------------------------- 1 | bits 16 2 | 3 | start: 4 | jmp main 5 | 6 | %include "std/defines.asm" 7 | %include "std/readdisk.asm" 8 | %include "std/writedisk.asm" 9 | %include "std/video.asm" 10 | %include "std/string.asm" 11 | %include "std/stdio.asm" 12 | %include "std/macros.asm" 13 | 14 | data: 15 | ; our stack and the current pointer 16 | memory times 256 db 0 17 | mem_idx db 0 18 | ; the code and the index of the current instruction 19 | buffer times 256 db 0 20 | buffer_index db 0 21 | ; to store 2 digits hex numbers 22 | hexnum times 3 db 0 23 | ; to store temp numbers to operate on them 24 | ts db 0 25 | ts1 db 0 26 | ; shell related 27 | cursor db 'plum> ', 0 28 | cmd_help db 'help', 0 29 | cmd_exit db 'exit', 0 30 | cmd_list db 'Commands: help exit', 13, 10, 0 31 | help_str db 'Language description', 13, 10 32 | db '====================', 13, 10 33 | db '! (print mem[p] until \0)', 13, 10 34 | db '? (read line, max:256 - p)', 13, 10 35 | db 'b[x] (p -= x) ; B (p--)', 13, 10 36 | db 'f[x] (p += x) ; F (p++)', 13, 10 37 | db 's[x] (mem[p] = x) ; S[...] (while code[i]!=$: mem[p++]=x)', 13, 10 38 | db 'i[x] (if mem[p] != 0, code index=x (absolute))', 13, 10 39 | db 'P (prints the value of the memory pointer)', 13, 10 40 | db 13, 10 41 | db 'operators: + - / * %', 13, 10 42 | db ' push TS op TS1', 13, 10 43 | db 'x: 2 hex digits [0-9a-fA-F] (unsigned)', 13, 10 44 | db 0 45 | ; messages 46 | msg_parsing_err db 'Could not interpret token at 0x', 0 47 | msg_memptr_mov_error db 'Could not move memory pointer: invalid destination', 13, 10, 0 48 | msg_input db '>>> ', 0 49 | 50 | main: 51 | ; set segments 52 | mov ax, cs 53 | mov ds, ax 54 | cld 55 | 56 | shell_begin: 57 | print cursor 58 | ; ask for user input 59 | input buffer, 255 60 | mov si, buffer 61 | 62 | ; check for commands before intepreting 63 | mov di, cmd_help 64 | call proj_e_compare_string 65 | jc .command_help 66 | 67 | mov di, cmd_exit 68 | call proj_e_compare_string 69 | jc .command_exit 70 | 71 | mov si, buffer 72 | jmp interpreter 73 | 74 | .command_help: 75 | print cmd_list 76 | print nl 77 | print help_str 78 | print nl 79 | jmp shell_begin 80 | 81 | .command_exit: 82 | jmp end 83 | 84 | interpreter: 85 | .main: 86 | ; if it is the end of the string, go back to the shell 87 | cmp byte [si], 0 88 | je shell_begin 89 | ; else, read a character and execute instruction 90 | ; '!': 33, '%': 37, '*': 42, '+': 43, '-': 45, '/': 47, '?': 63, 'B': 66 91 | ; 'F': 70, 'P': 80, 'S': 83, 'b': 98, 'f': 102, 'i': 105, 's': 115 92 | cmp byte [si], 33 ; if byte <> '!' 93 | jl .error ; lower => error 94 | je .print ; equal => ok 95 | ; greater => 96 | cmp byte [si], 37 ; if byte <> '%' 97 | jl .error 98 | je .modulo 99 | ; greater => 100 | cmp byte [si], 42 ; if byte <> '*' 101 | jl .error 102 | je .multiply 103 | ; greater => 104 | cmp byte [si], 43 ; if byte <> '+' (can not be lower if we're here) 105 | je .addition 106 | ; greater => 107 | cmp byte [si], 45 ; if byte <> '-' 108 | jl .error 109 | je .substract 110 | ; greater => 111 | cmp byte [si], 47 ; if byte <> '/' 112 | jl .error 113 | je .divide 114 | ; greater => 115 | cmp byte [si], 63 ; if byte <> '?' 116 | jl .error 117 | je .readline 118 | ; greater => 119 | cmp byte [si], 66 ; if byte <> 'B' 120 | jl .error 121 | je .backward 122 | ; greater => 123 | cmp byte [si], 70 ; if byte <> 'F' 124 | jl .error 125 | je .forward 126 | ; greater => 127 | cmp byte [si], 80 ; if byte <> 'P' 128 | jl .error 129 | je .print_ptr 130 | ; greater => 131 | cmp byte [si], 83 ; if byte <> 'S' 132 | jl .error 133 | je .store 134 | ; greater => 135 | cmp byte [si], 98 ; if byte <> 'b' 136 | jl .error 137 | je .backward_args 138 | ; greater => 139 | cmp byte [si], 102 ; if byte <> 'f' 140 | jl .error 141 | je .forward_args 142 | ; greater => 143 | cmp byte [si], 105 ; if byte <> 'i' 144 | jl .error 145 | je .if 146 | ; greater => 147 | cmp byte [si], 115 ; if byte <> 's' 148 | jl .error 149 | je .store_one 150 | jg .error 151 | 152 | .print: 153 | push si 154 | mov si, memory 155 | xor ch, ch 156 | mov cl, byte [mem_idx] 157 | add si, cx 158 | mov ah, 0x0e ; specify int 0x10 (teletype output) 159 | .printchar: 160 | lodsb ; load byte from si into al, increment si 161 | cmp al, 0 ; is it the end of the string ? 162 | je .print_done ; yes => quit ; no => continue 163 | int 0x10 ; print the character 164 | jmp .printchar 165 | .print_done: 166 | print nl 167 | pop si 168 | jmp .next 169 | 170 | .modulo: 171 | call .retrieve_2_stack_val 172 | push si 173 | mov si, memory 174 | 175 | xor dx, dx 176 | xor ah, ah 177 | mov byte al, byte [ts] 178 | xor bh, bh 179 | mov byte bl, byte [ts1] 180 | div bx ; divides AX by BX 181 | mov byte dl, byte al ; store modulo result in AL 182 | 183 | ; storing AL in SI 184 | xor ch, ch 185 | mov cl, byte [mem_idx] 186 | add si, cx 187 | mov [si], al 188 | pop si 189 | jmp .next 190 | 191 | .multiply: 192 | call .retrieve_2_stack_val 193 | push si 194 | mov si, memory 195 | 196 | xor ah, ah 197 | mov byte al, byte [ts] 198 | mov byte bl, byte [ts1] 199 | mul bl 200 | ; result is in AX 201 | 202 | ; storing AL in SI 203 | xor ch, ch 204 | mov cl, byte [mem_idx] 205 | add si, cx 206 | mov [si], al 207 | pop si 208 | jmp .next 209 | 210 | .addition: 211 | call .retrieve_2_stack_val 212 | push si 213 | mov si, memory 214 | 215 | xor ah, ah 216 | mov byte al, byte [ts] 217 | xor bh, bh 218 | mov byte bl, byte [ts1] 219 | add ax, bx 220 | ; result is in AX 221 | 222 | ; storing AL in SI 223 | xor ch, ch 224 | mov cl, byte [mem_idx] 225 | add si, cx 226 | mov [si], al 227 | pop si 228 | jmp .next 229 | 230 | .substract: 231 | call .retrieve_2_stack_val 232 | push si 233 | mov si, memory 234 | 235 | xor ah, ah 236 | mov byte al, byte [ts] 237 | xor bh, bh 238 | mov byte bl, byte [ts1] 239 | sub ax, bx 240 | ; result is in AX 241 | 242 | ; storing AL in SI 243 | xor ch, ch 244 | mov cl, byte [mem_idx] 245 | add si, cx 246 | mov [si], al 247 | pop si 248 | jmp .next 249 | 250 | .divide: 251 | call .retrieve_2_stack_val 252 | push si 253 | mov si, memory 254 | 255 | xor ah, ah 256 | mov byte al, byte [ts] 257 | xor bh, bh 258 | mov byte bl, byte [ts1] 259 | div bx ; divides AX by BX 260 | ; result is in AX 261 | 262 | ; storing AL in SI 263 | xor ch, ch 264 | mov cl, byte [mem_idx] 265 | add si, cx 266 | mov [si], al 267 | pop si 268 | jmp .next 269 | 270 | .readline: 271 | print msg_input 272 | 273 | push si 274 | mov byte cl, byte [mem_idx] 275 | ; store our memory in SI 276 | mov si, memory 277 | 278 | .getch: 279 | ; no point entering a character if memory is full 280 | cmp byte cl, 0xff 281 | je .end_reading 282 | 283 | xor ah, ah 284 | int 0x16 ; character read will be in AL 285 | 286 | cmp byte al, 0x0d ; enter pressed 287 | je .end_reading 288 | 289 | ; display character 290 | mov ah, 0x0e ; Teletype mode 291 | int 0x10 ; Print interrupt 292 | ; save character (which is in AX) in memory 293 | mystosb si 294 | inc byte cl 295 | jmp .getch 296 | 297 | .end_reading: 298 | mov byte [mem_idx], byte cl 299 | pop si 300 | print nl 301 | jmp .next 302 | 303 | .backward: 304 | cmp byte [mem_idx], 0x00 305 | je .backward_error 306 | dec byte [mem_idx] 307 | jmp .next 308 | 309 | .backward_error: 310 | print msg_memptr_mov_error 311 | jmp shell_begin 312 | 313 | .forward: 314 | cmp byte [mem_idx], 0xff 315 | je .forward_error 316 | inc byte [mem_idx] 317 | jmp .next 318 | 319 | .forward_error: 320 | print msg_memptr_mov_error 321 | jmp shell_begin 322 | 323 | .print_ptr: 324 | xor ah, ah 325 | mov byte al, byte [mem_idx] 326 | call proj_e_print_hex 327 | push si 328 | print nl 329 | pop si 330 | jmp .next 331 | 332 | .store: 333 | mov di, memory 334 | ; moving the cursor 335 | xor ch, ch 336 | mov cl, [mem_idx] 337 | add di, cx ; move to current memory 338 | call .move_in_buffer 339 | mov byte al, [si] 340 | cmp byte al, '$' ; if character is '$', it is the end 341 | je .next 342 | mystosb di ; storing character as-is in memory 343 | ; going forward in the memory 344 | cmp byte [mem_idx], 0xff 345 | je .forward_error ; if we can't, throw an error 346 | inc byte [mem_idx] 347 | jmp .store 348 | 349 | .backward_args: 350 | call .readhexval 351 | ; checking if we can substract ax from cx 352 | xor ch, ch 353 | mov cl, byte [mem_idx] 354 | cmp cx, ax 355 | jl .backward_error 356 | sub byte [mem_idx], byte al 357 | jmp .next 358 | 359 | .forward_args: 360 | call .readhexval 361 | ; checking if we can add ax to cx 362 | xor ch, ch 363 | mov cl, 0xff 364 | sub cl, byte [mem_idx] 365 | cmp cx, ax 366 | jl .forward_error 367 | add byte [mem_idx], byte al 368 | jmp .next 369 | 370 | .if: 371 | push si 372 | mov si, memory 373 | ; move in the memory to compare the current memory thing 374 | xor ch, ch 375 | mov cl, byte [mem_idx] 376 | add si, cx 377 | cmp byte [si], 0x00 378 | je .equal 379 | pop si 380 | jmp .next 381 | 382 | .equal: 383 | pop si 384 | call .readhexval 385 | ; reset buffer current character pointed 386 | mov si, buffer 387 | mov byte [buffer_index], 0x00 388 | ; set up counter 389 | mov cx, ax 390 | .loop: 391 | call .move_in_buffer 392 | dec cx 393 | cmp cx, 0x00 394 | jne .loop 395 | jmp interpreter 396 | 397 | .store_one: 398 | call .readhexval 399 | 400 | mov di, memory 401 | ; moving the cursor 402 | xor ch, ch 403 | mov cl, [mem_idx] 404 | add di, cx ; move to current memory 405 | mystosb di ; store ax in di 406 | inc byte [mem_idx] ; move memory pointer 407 | 408 | jmp .next 409 | 410 | .error: 411 | print msg_parsing_err 412 | ; print the offset of the unknown character in the last buffer 413 | xor ah, ah 414 | printhex [buffer_index] 415 | print nl 416 | jmp shell_begin 417 | 418 | .next: 419 | call .move_in_buffer 420 | jmp interpreter 421 | 422 | .move_in_buffer: 423 | inc si 424 | inc byte [buffer_index] 425 | ret 426 | 427 | .readhexval: 428 | ; number will be in AL 429 | push di 430 | ; going to copy the hexnumber to a temporary buffer 431 | mov di, hexnum 432 | xor ah, ah 433 | 434 | ; going foward in the buffer to fetch a character 435 | call .move_in_buffer 436 | mov byte al, [si] 437 | mov [di], byte al 438 | 439 | ; going forward in our temporary buffer 440 | inc di 441 | ; going forward in the buffer to fetch the next character 442 | call .move_in_buffer 443 | mov byte al, [si] 444 | mov [di], byte al 445 | ; going back on the first character of the string in the temporary buffer 446 | dec di 447 | 448 | push si 449 | ; copying di to si to transform the string to a number 450 | mov si, di 451 | call proj_e_2hex_to_int 452 | pop si 453 | pop di 454 | 455 | ret 456 | 457 | .retrieve_2_stack_val: 458 | push di 459 | mov di, memory 460 | 461 | xor ch, ch 462 | mov cl, byte [mem_idx] 463 | add di, cx 464 | 465 | dec di 466 | mov byte al, [di] 467 | mov byte [ts], byte al 468 | 469 | dec di 470 | mov byte al, [di] 471 | mov byte [ts1], byte al 472 | 473 | ;inc di 474 | ;inc byte [mem_idx] 475 | pop di 476 | 477 | ret 478 | 479 | end: 480 | jmp KERNEL_LOAD_ADDRESS:0x0000 481 | 482 | ; 16 kB application 483 | times 16384-($-$$) db 0 484 | --------------------------------------------------------------------------------