NASM - The Netwide Assembler
NASM Forum => Using NASM => Topic started by: mare011 on November 02, 2010, 12:49:20 PM
-
Can someone help me, i need to reduce my code to be < 150 bytes when compile it in .com
The program works in real mode, and it draws the time in the upper right corrner and it's TSR.
I basicly install my interupt vector 1c that writes the time with yellow color, i'm writing directly in graphic memory, cause i'm forbbiden to use int 21h, and then i call 27h so it becomes TSR
My code is now 165 bytes, i need to lose 15 bytes, does anyone have an idea how to do it, i basicly stript every redundant code i could find, but i'm not experiensed in assembly, so i guss that there's a liter way of doing some instructions.
Here's the code:
org 100h
segment .code
main:
call _install_1C
mov dx, load
int 27h
; -----------------------------------------------------------------------
; Install vector 1c
; -----------------------------------------------------------------------
_install_1C:
;cli
xor ax, ax
mov es, ax
; Point vector table to my rutine
mov dx, timer_int
mov [es:1Ch*4], dx
mov ax, cs
mov [es:1Ch*4+2], ax
push ds
pop gs
sti
ret
timer_int:
pushaw
push gs
pop ds
; Timer interupt code
call _get_time ; my function to get time
call _print_time ; my function to print time
izlaz:
popaw
iret
; -----------------------------------------------------------------------
; _get_time_string -- gets time in format: hh:mm:ss
; -----------------------------------------------------------------------
%define T_SEPARATOR ':'
_get_time:
pusha
mov di, 0
mov ah, 2 ; Getting BIOS time in BCD
int 1Ah ; CH: hours, CL: minutes, DH: seconds
mov al, ch ; Hours
shr al, 4
and ch, 0Fh
call AddDigit
mov al, ch
call AddDigit
mov al, T_SEPARATOR
call AddSymbol
mov al, cl
shr al, 4
and cl, 0Fh
call AddDigit
mov al, cl
call AddDigit
mov al, T_SEPARATOR
call AddSymbol
mov al, dh ; Secunds
shr al, 4
and dh, 0Fh
call AddDigit
mov al, dh
call AddDigit
mov al, 0 ; end of string
call AddSymbol
popa
ret
AddDigit:
add al, '0' ; coverts AL into ASCII
mov [vreme + di], al
inc di
ret
AddSymbol:
mov [vreme + di], al
inc di
ret
; -----------------------------------------------------------------------
;Putting time in video memory
; -----------------------------------------------------------------------
zuta equ 0Eh ; Yellow color
_print_time:
push es
mov ax, 0B800h ; Baze adress of video momory = B8000h
mov es, ax
mov bx, word 144 ; Video position = end of line 2 is 160 - 16 bayte, 8 position since one position uses 2 bytes.
mov si, vreme ; si point to array 'vreme' (time)
.prn:
mov al, byte [si]
cmp al, 0
je .end
mov byte [es:bx], al
inc bx
mov al, zuta
mov byte [es:bx], al
inc bx
inc si
jmp .prn
.end:
pop es
ret
load:
segment .data
vreme: db 0
Thanks in advance
-
Briefly looking at your code, the following snippet optimizes out a few operations for you:
AddDigit:
add al, '0' ; coverts AL into ASCII
AddSymbol:
mov [vreme + di], al
inc di
ret
-
Nice job. It's a shame to uglify it to save a few bytes. But there are some things you can do (or not do)...
org 100h
segment .code
main:
call _install_1C
mov dx, load
int 27h
; -----------------------------------------------------------------------
; Install vector 1c
; -----------------------------------------------------------------------
_install_1C:
;cli
xor ax, ax
mov es, ax
; Point vector table to my rutine
mov dx, timer_int
mov [es:1Ch*4], dx
Could be:
mov word [es:1Ch * 4], timer_int
And...
mov ax, cs
mov [es:1Ch*4+2], ax
Could be:
mov [es:1Ch * 4 + 2], cs
push ds
pop gs
sti
ret
I think I see why you "push ds"/"pop gs", but you don't really need to do it. Might not work, anyway. When the interrupt is triggered, we don't know what ds might have been, so we want to set it to a known value. But gs might have been changed, too! We do know what cs is, and we want ds to be the same, so we could use cs instead of gs here. That doesn't save any bytes (except we didn't need to do gs, above). Worse, pushaw/popaw doesn't preserve segment registers, and we need to do it, so add "push ds" and "pop ds" at the end (of your timer_int). We're losing ground! :(
I notice that you've got "cli" commented out. Was it causing a problem? We don't want that interrupt to occur when we've changed the offset but not the segment! But Windows may not like "cli"(?). If that's a problem, we could do it "atomically":
...
mov ax, cs
shl eax, 16
mov ax, timer_int
mov [es:1Ch * 4], eax
...
Yeah, you can use 32-bit registers in 16-bit code. It requires an "operand size override prefix" - 66h - which Nasm will provide. I'd stick with the "cli", if it works.
timer_int:
pushaw
push gs
pop ds
; Timer interupt code
call _get_time ; my function to get time
call _print_time ; my function to print time
izlaz:
popaw
iret
; -----------------------------------------------------------------------
; _get_time_string -- gets time in format: hh:mm:ss
; -----------------------------------------------------------------------
%define T_SEPARATOR ':'
_get_time:
pusha
mov di, 0
mov ah, 2 ; Getting BIOS time in BCD
int 1Ah ; CH: hours, CL: minutes, DH: seconds
mov al, ch ; Hours
shr al, 4
and ch, 0Fh
call AddDigit
mov al, ch
call AddDigit
mov al, T_SEPARATOR
call AddSymbol
mov al, cl
shr al, 4
and cl, 0Fh
call AddDigit
mov al, cl
call AddDigit
mov al, T_SEPARATOR
call AddSymbol
mov al, dh ; Secunds
shr al, 4
and dh, 0Fh
call AddDigit
mov al, dh
call AddDigit
mov al, 0 ; end of string
call AddSymbol
popa
ret
AddDigit:
add al, '0' ; coverts AL into ASCII
mov [vreme + di], al
inc di
ret
AddSymbol:
mov [vreme + di], al
inc di
ret
; -----------------------------------------------------------------------
;Putting time in video memory
; -----------------------------------------------------------------------
zuta equ 0Eh ; Yellow color
_print_time:
push es
mov ax, 0B800h ; Baze adress of video momory = B8000h
mov es, ax
mov bx, word 144 ; Video position = end of line 2 is 160 - 16 bayte, 8 position since one position uses 2 bytes.
mov si, vreme ; si point to array 'vreme' (time)
.prn:
mov al, byte [si]
cmp al, 0
je .end
mov byte [es:bx], al
inc bx
mov al, zuta
mov byte [es:bx], al
inc bx
inc si
jmp .prn
.end:
pop es
ret
load:
segment .data
vreme: db 0
You might want to reserve a bigger buffer for "vreme" - you're putting 8 bytes into it. Accumulating your "string" and printing it "direct to screen" as you do is a nice "clean" way to do it. But, strictly speaking, you don't need to do that - you could put the digits and ':'s right to the screen, instead of into your string. This saves a bunch of bytes - "don't do it at all" is shorter, faster... even easier. But it's uglier (IMO)!
The so-called "string instructions" are nice and short. "lodsb" replaces "mov al, [si]" and "inc si". "stosb" replaces "mov [es:di]" and "inc si". "stosw" is handy here - if we've got the color in ah, and the character in al, and the video segment in es, and the desired offset in di, then "stosw" will print character and color, and add 2 to di, all in one byte! You use "[es:bx]", but "[es:di]" would work, then you could use "stosb" twice, or "stosw" once...
I've mutilated your code into this - untested(!) which assembles into about 80 bytes - a little more could be done, I think, but this is "short enough" (maybe... that "int 27h" you use is "obsolete" and has some limitations...)
org 100h
%define T_SEPARATOR ':'
zuta equ 0Eh ; Yellow color
segment .text
main:
call _install_1C
mov dx, load
int 27h
; -----------------------------------------------------------------------
; Install vector 1c
; -----------------------------------------------------------------------
_install_1C:
cli
xor ax, ax
mov es, ax
; Point vector table to my rutine
mov word [es:1Ch * 4], timer_int
mov [es:1Ch*4+2], cs
sti
ret
; Timer interupt code
timer_int:
pushaw
push es
push 0B800h
pop es
mov ah, 2 ; Getting BIOS time in BCD
int 1Ah ; CH: hours, CL: minutes, DH: seconds
mov ah, zuta
mov di, word 144 ; Video position = end of line 2 is 160 - 16 bayte, 8 position since one position uses 2 bytes.
mov al, ch ; Hours
call print_two_digits
mov al, T_SEPARATOR
stosw
mov al, cl
call print_two_digits
mov al, T_SEPARATOR
stosw
mov al, dh ; Secunds
call print_two_digits
izlaz:
pop es
popaw
iret
print_two_digits:
; expects: char in al, color in ah, video segment in es, offset in di
; returns: al trashed, di set to next position
push ax
shr al, 4
add al, '0'
stosw
pop ax
and al, 0Fh
add al, '0'
stosw
ret
load:
Since I've eliminated the string, we don't even care what ds is in the interrupt routine, so I've eliminated that, too... I really think your code is a lot "nicer". Perhaps you can develop a "hybrid" that works...
Best,
Frank
-
Wow, thanks Frank! It all looks so logical and simple, but i would probably never think of writing it that way. You really help me a lot, plus I've really learned a lot by looking at your code and comments. BTW i'd commented the "cli" instuction to see how much bytes will it free up, and i forgot to uncomment it :)