3
\$\begingroup\$

I made a very simple OS in Assembly. But my code is too long. I want to make my code shorter.

Here is my code:

    BITS 16

start:
    mov ax, 07C0h
    add ax, 288
    mov ss, ax
    mov sp, 4096

    mov ax, 07C0h
    mov ds, ax


    mov si, text_string
    call print_string

    jmp $


    text_string db 'OS', 0


print_string:
    mov ah, 0Eh

.repeat:
    lodsb
    cmp al, 0
    je .done
    int 10h
    jmp .repeat

.done:
    ret


    times 510-($-$$) db 0
    dw 0xAA55

Makefile:

DIR=build build
$(shell mkdir -p $(DIR))
$(shell nasm -f bin OS1.asm -o build/OS1.flp)
$(shell mkisofs -no-emul-boot -o build/OS1.iso -b OS1.flp build)

Can I make this code shorter? Is it possible to do? If it is, how?

(This code may be updated in my GitHub account.)

\$\endgroup\$
  • 2
    \$\begingroup\$ If you scale this OS up, then the way to make it shorter is to compile (most of) it in C, with assembly sections only as needed. \$\endgroup\$ – Reinderien Nov 20 at 16:37
  • \$\begingroup\$ What do you mean with "OS"? \$\endgroup\$ – superb rain Nov 21 at 19:57
  • \$\begingroup\$ @superbrain Operating System \$\endgroup\$ – Arian Nov 22 at 7:24
  • 1
    \$\begingroup\$ Ok, that's what I thought. But an operating system in 20 lines of assembly rather seems way too short to be true rather than too long. I wrote a lot of assembly for years but that was over 20 years ago, so now I can't understand what your code does but I'm curious. What does it do? \$\endgroup\$ – superb rain Nov 22 at 13:06
5
\$\begingroup\$

Can I make this code shorter?

  • If you use the ORG 7C00h directive, then loading the segment registers will be shorter code.
  • You can choose to setup the stack below the bootloader and shave off that addition. Even if you leave the stack where you placed it before (above the bootloader), with SS=0 the change would become mov sp, 9E00h.
  • You avoid having to load the string address (and the need to come up with suitable label names) if you store the actual string directly beneath the call instruction and have the print_string routine jump back to behind the terminating zero.
    This technique becomes more interesting if more than 1 string is to be printed.
    BITS  16
    ORG   7C00h

start:
    cld                          <--- Not shorter but better!
    xor   ax, ax
    mov   ds, ax
    mov   ss, ax
    mov   sp, 7C00h

    call  print_string
    db    'OS', 0
    jmp   $

print_string:
    pop   si
    mov   bx, 7                  <--- Not shorter but better!
    mov   ah, 0Eh
.repeat:
    cs lodsb ; Need CS override since SI is an offset in code segment!
    cmp   al, 0
    je    .done
    int   10h
    jmp   .repeat
.done:
    jmp   si


    times 510-($-$$) db 0
    dw 0xAA55

The CS segment override on the LODSB instruction is required unless you know that DS == CS. Many bootloaders will have a far jump at the top to ensure that this is the case. e.g. jmp 0000h:7C05h as the very first instruction. This will set CS=0.

Not shorter but better

  • Adding a CLD instruction makes sure that the LODSB instruction will function correctly.
  • Passing all the required parameters to the BIOS.Teletype function is best. BH is DisplayPage and BL is GraphicsColor. If you know that the screen is in a text video mode then you can drop the setting for BL and just write mov bh, 0 to select the video page.
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

You initialize ax with the same value twice, so you could save one command by rearranging the code bit:

mov ax, 07C0h
mov ds, ax
add ax, 288
mov ss, ax

You could save 5 commands if you give up on print_string being a callable function and just inline its code. You won't need to initialize the stack registers and you won't need the call and ret commands.

Of course, this will preclude reuse of the function if you decide to expand your OS.

There is also a middle ground, though I think it is a bit hacky:
Keep a "function like" structure for print_string but avoid using call and ret.

Instead, save the return address to dx and use jmp.

I haven't actually tested this, but it does compile and should work:

   BITS 16

start:
    mov ax, 07C0h
    mov ds, ax

    mov si, text_string
    mov dx, return_target
    jmp print_string
return_target:
    jmp $


    text_string db 'OS', 0


print_string:
    mov ah, 0Eh

.repeat:
    lodsb
    cmp al, 0
    jne .do_print
    jmp dx

.do_print:
    int 10h
    jmp .repeat

You will be essentially creating your own non-standard calling convention, but you will save 3 commands as a result and still keep your printing code reusable.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I see the benefit from not spending instructions on creating a stack but why avoid using a stack? The int 10h already uses the stack and so can call print_string. \$\endgroup\$ – Sep Roland Nov 21 at 17:20
  • \$\begingroup\$ @SepRoland the OP asked how to make the code shorter, I interpreted it as "do the same thing with less instructions". I don't claim this code to be good in any way. Regarding the int instruction, I may have forgoten some some details as it has been a long time since I touched on assembly... So to be clear: will my proposed code result in some undefined behavior when int 10h is called due to stack registers being in unknown state? \$\endgroup\$ – Lev M. Nov 21 at 17:47
  • 1
    \$\begingroup\$ The BIOS will have set up a stack, but it might not be very large (the original IBM PC used 256 bytes at the end of the Interrupt Vector Table). So while you can get away without setting up your own stack here, you cannot rely on using it for very long. \$\endgroup\$ – 1201ProgramAlarm Nov 21 at 18:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.