cristhiangrundmann / GrOS

Maybe the world's smallest hex editor
GNU General Public License v3.0
6 stars 0 forks source link

Alternates to F8,F9,F12 #1

Closed ddtopham closed 3 years ago

ddtopham commented 3 years ago

Hi Cristhian, I really like this idea. I was able to load the bin file into a wonderful browser virtual machine written by Fabrice Bellard and it runs well (JSLinux): but, because it is in browser, the Alt keys are intercepted by the browser and therefore can't be used. How could I change the source so that it uses some other keys for "save", "read" etc.?

I did read the source, but (no comments) so not clear to me where those keys are being read and decoded.

cristhiangrundmann commented 3 years ago

I put comments for the key values. The instructions that detects the keys, cmp al, ..., now have a comment telling which key that value means.

You can consult the table for the scan codes here: https://www.stanislavs.org/helppc/scan_codes.html

The key for the swap function (currently tab) and the key for calling the code on buffer (currently F12) can be changed without problems at lines 155 and 207.

The keys for reading and writing are special. Their values were chosen because they are also relevant for the int 0x13. By choosing these values, I didn't have to set the AH register before calling the interrupt. This coincidence ended up saving 4 bytes.

Since I chose to maintain the partition table alive, the space for code is really tight. At the moment, there is no free byte!

So, to change the keys for reading and writing, you can comment the partition table (from lines 288 to 295). This will save 64 bytes!

You also will need to uncomment the lines 161 and 172, to properly set the AH register.

If you do this then you can change the keys at lines 164 and 168. You have to consider that at this point, AH= AL and they both hold the scan code of the key pressed. If you want to use an ASCII key, you will have to save AL after line 94. Now that the partition table is gone, there is plenty of space for that.

It's been a while since I coded this, so I can't be sure that this is 100% correct.

ddtopham commented 3 years ago

Thank you, this seems to be working ok. I realized if I use similar scan codes to the Alt Fx keys, I can drop in replacements and not have to remove the partition table. i.e. Use PgUp for F8, PgDn for F9, and Ins for F12 (tab and arrows work fine already).

I don't quite get how to read in a sector. e.g. I am using a single 512-byte "disk", so there is no other sector. I tried various numbers to represent the current sector as an 8-byte LBA, but no luck "reading" or "writing" it so far. Should I able to read the boot sector itself and modify it, then write it back to change the editor itself?

cristhiangrundmann commented 3 years ago

If you read the first sector, edit and then save it, you'll have to reboot the system for the code to change, because the running code at 0x7c00 isn't affected at all, only the buffer at 0x8000 is. So a reboot will load the updated code in the right address.

The 8-byte address in the screen is the LBA address used in both read and write operations. It's displayed in little-endian in the screen. The interrupt 0x13 with AH = 0x42 or 0x43 demands an 8-byte address.

If a read or write fails, the cursor will be moved to a specific spot.

ddtopham commented 3 years ago

It is so hard to debug boot sector code since there is no debugger to see what is returned from functions. I think the int 16 0x42 is reading (when I press 0x49 PgUp), but I don't ever see anything but zeros in the editor, so perhaps not. When editor opens should it already be displaying the boot sector? From what I read, the MBR should be LBA 0. When I set the upper top numbers to all zeros, then try to read, I don't see anything. You mention is uses little endian, so to read the 2nd sector, would that be 0000000000000001 or 0100000000000000 ?

cristhiangrundmann commented 3 years ago

When booted, the editor should display the data from the buffer at 0x8000, which initially all zeroes in most cases. The editor doesn't read anything from disk when initializing.

The second sector has LBA 1, so it's 0100000000000000. This is actually the initial value for the LBA.

When you write something on the screen, in the LBA address or in the buffer, the data changed should have the color changed. When a read or write is triggered, the screen should be redrawn and the colors reset. That should happen even if the read/write failed. That will help to see if the page keys are actually working.

ddtopham commented 3 years ago

I added a second sector (so LBA 1 is valid) and put some data in it. As you described, the colors change, so I can enter some new chars in buffer, press button to write and do see the colors change from white to black and get reset in hex area. I can continue making more edits too I added a "division by zero" if CF was set after int 13. That didn't trigger an error, so all seemed OK, but still, when I exit and check the bin image, there are no changes recorded. Also, reading from that sector does not bring in the chars I had placed there. What is significance of the DAP - 0x7000 ? I also noticed an online suggestion to put the drive number in DL (disk number for emulator is 0x80), but that didn't seem to affect anything. Here is what I have so far:

org 0x7c00
bits 16

%define SECTORS 0x2

%define BCOLORAS 0x20
%define BCOLORH1 0x10
%define BCOLORH2 0x30
%define BCOLORCU 0x40
%define FCOLORDE 0x00
%define FCOLORED 0x0f

    ;mov ah, 0x5
    ;mov cx, 0x4200
    ;int 0x16

main:
    cld
    sti
    xor ax, ax
    mov bp, ax
    mov sp, bp
    mov ah, 0x07
    mov ds, ax
    mov ss, ax
    mov ah, 0xb8
    mov es, ax

    mov ax, 0x3
    int 0x10
    mov ax, 0x1112
    mov bl, 0x0
    int 0x10

draw:
    pusha
    push es
    mov bx, 0xb800 + 0xa*0x2
    mov es, bx
    mov ah, FCOLORDE
    mov si, TARGET - 0x7000
    mov di, 0x20
    mov cl, 0x8
draw0:
    lodsb
    call type
    dec cl
    jnz draw0
    mov bl, 0xa*0x4
    mov es, bx
    xor si, si
    mov ch, 0x20
draw1:
    mov cl, 0x10
    mov di, 0x20
draw2:
    lodsb
    call type
    dec cl
    jnz draw2
    add bx, 0xa
    mov es, bx
    dec ch
    jnz draw1
    pop es
    popa

mloop:
    and di, 0x7f

mloopc0:

    mov ax, es
    cmp ax, 0xb800 + 0xa*0x2
    jnl mloopc2
    mov ax, 0xb800 + 0xa * 0x23
mloopc2:
    cmp ax, 0xb800 + 0xa * 0x24
    jb mloopc3
    mov ax, 0xb800 + 0xa*0x2
mloopc3:
    mov es, ax

    mov ax, [es:di]
    push ax
    and ah, 0x0f
    or ah, BCOLORCU
    mov [es:di], ax

    xor ax, ax
    int 0x16

    pop word [es:di]

    cmp al, 0x20
    jb mloop1

    mov bx, [es:di]
    cmp bh, 0x10
    jb mloop

    cmp di, 0x40
    jb mloop0
    mov ah, al
    call hexascii
    and al, 0x0f
    call hexascii
    cmp ah, al
    jne mloop

    push di
    stosb
    mov cl, 0x1
    mov si, mloop_addr - 0x7000 + 0x1
    and di, 0xffff - 0x3
    call read
    call swap
mloop_addr:
    mov ax, FCOLORED * 0x100 + '?'
    call type
    pop di
mloop_incdi:
    inc di
    inc di
    jmp mloop
mloop0:
    mov ah, FCOLORED
    call type
    jmp mloop
mloop1:
    mov al, ah
    cmp al, 0x4b
    jne mloop2
    dec di
    dec di
    jmp mloop
mloop2:
    cmp al, 0x4d
    je mloop_incdi
    cmp al, 0x48
    jne mloop3
    mov cx, es
    sub cx, 0xa
    mov es, cx
mloop3:
    cmp al, 0x50
    jne mloop4
    mov cx, es
    add cx, 0xa
    mov es, cx
mloop4:
    cmp al, 0x0f
    jne mloop5
    call swap
mloop5:
    cmp al, 0x51 ; = PgDn, F8 = 0x42
    mov ah, 0x42 ; extended bios read
    je mloop7
    cmp al, 0x49 ; = PgUp, F9 = 0x43
    mov ah, 0x43 ; extended bios write
    jne mloop8
    pusha
    push es
    push 0xb800
    pop es
    mov di, 0xa0*0x4 + 0x40
    mov cl, 0x10
    xor si, si
mloop6:
    call read
    add di, 0xa0
    add si, 0x10
    cmp si, 0x200
    jne mloop6
    pop es
    popa
mloop7:
    pusha
    push es
    push 0xb800 + 0xa*0x2
    pop es
    mov cl, 0x8
    mov si, TARGET - 0x7000
    mov di, 0x40
    call read
    pop es
    mov al, 0x0
    mov si, DAP - 0x7000
mov dl,0x80
    int 0x13

jnc ok
    mov bx,0
    div bx
ok:
    jc main
    popa
    jmp draw
mloop8:
    cmp al, 0x52 ; = Ins  F12 = 0x86
    jne mloop
    xor si, si
    lodsw
    cmp ax, 0x9090
    jne mloop
    call 0x7000
    jmp mloop

read:
    pusha
read0:
    mov ax, [es:di]
    stosw
    call hexascii
    shl al, 0x4
    mov dh, al
    mov ax, [es:di]
    stosw
    call hexascii
    and al, 0x0f
    or al, dh
    mov [si], al
    inc si
    dec cl
    jnz read0
    popa
    ret

type:
    push ax
    or ah, BCOLORAS
    push di
    stosw
    xor ah, BCOLORAS ^ BCOLORH1
    shr di, 0x2
    jnc type0
    xor ah, BCOLORH1 ^ BCOLORH2
type0:
    pop di
    call swap
    push ax
    shr al, 0x4
    call hexascii
    stosw
    pop ax
    and al, 0x0f
    call hexascii
    stosw
    call swap
    pop ax
    ret

swap:
    cmp di, 0x40
    jl swap0
    shr di, 0x2
swap0:
    shl di, 0x1
    ret

hexascii:
    xor al, 0x30
    cmp al, 0x3a
    jb hexascii0
    add al, 0x99
    jc hexascii0
    sub al, 0x92
hexascii0:
    ret

DAP:
db 0x10
db 0x0
dw 0x1
dw 0x0
dw 0x0700
TARGET: dq 0x1
%if 0
times (0x1be - ($ - $$)) db 0xA3

PARTITION_STATUS: db 0x80
CHS_START: db 0x0, 0x0, 0x0
PARTITION_TYPE: db 0xff
CHS_END: db 0x0, 0x0, 0x0
LBA_START: dd 0x0
VOLUME_SIZE: dd SECTORS
%endif
times 0x1fe-($-$$) db 0x0
db 0x55, 0xaa
times 512 db 0x41
cristhiangrundmann commented 3 years ago

The reading and writing issue could be coming from the virtual machine. I really don't know. Is reading the MBR (LBA 0) working?

The DS:SI address should point to the DAP structure to be used by the int 0x13 interrupt, which is after the hexascii function in memory. The DS register is set in the beginning of the program to 0x700, and the SI is set to DAP - 0x7000.

The memory address of SEGMENT:OFFSET points to the memory at SEGMENT * 16 + OFFSET. The DAP in compile-time is correct if the segment is 0x0. This is why I subtract 0x7000 from DAP.

Also, I said previously that the buffer is at 0x8000 but it's in fact at 0x7000, my bad.

The int 0x13 interrupt uses DL to indicate what disk to use. When BIOS loads the bootsector code from a disk, the DL register holds the disk number of the same disk. The editor code never uses this register for anything, only the interrupt does. So DL always holds the disk number, unless it's changed after the F12 function.

You also posted the whole source code, but since you didn't put it between ```, the * and some code got messed up.

ddtopham commented 3 years ago

Thank you for the details above. I understand much better.

Progress: I am not entirely sure why, but this morning I see different behavior! Now I can read the sectors. I do see the MBR and my second sector when I read them. I can change things and see the buffer char color change to black. When I read it back, I still see those changes.

But still, after exiting the virtual machine (TinyEMU), the disk is unchanged. There is no carry set after write so it appears to "write" but no change is actually made to disk.bin file.

I set first 2 bytes to 9090 and pressed key to execute code - as expected that hangs (because I don't have any "real" code in that second sector. (Can you suggest how you might use that? e.g. What code could you put in a second sector and verify that it runs correctly?)

Is there any way to "step" through the bootloader?

cristhiangrundmann commented 3 years ago

Maybe the VM is simply preserving the disk and writing in another file.

A nice test code is 90 90 B4 0E B0 21 CD 10 C3, which means:

nop ;90
nop ;90
mov ah, 0x0e ;B4 0E
mov al, '!' ;b0 21
int 0x10 ;CD 10
ret ;C3

This code should type a ! at the "true" cursor, at top left. Since this code returns, you can keep editing and calling this code multiple times. The code that will run is the code at the buffer. If a change isn't saved, the buffer won't be updated. You can simply write the code somewhere on disk. The buffer will be updated even if the operation failed.

About debugging: I only use qemu, and I don't know how to step the code. I only know how to dump memory from RAM and registers.

ddtopham commented 3 years ago

That works perfectly! Very nice. I installed Qemu and found that the save does work there, so you are probably right that the TinyEMU emulator is blocking the write.

Thank you very much for helping me to get up to speed on this code. I learned a lot and can experiment with bootsectors more easily. Quite a challenge to fit an editor into 512 bytes.

ddtopham commented 3 years ago

I was able to get write sector to work by adding parameters to TinyEMU. You can now try this editor without installing anything to your computer at all! Thanks to Fabrice Bellard's wonderful JSLinux. It takes about 15-20 seconds for Alpine Linux to boot up in your browser, then you can try GrOS right away: