Assembly Programming Journal 7
Assembly Programming Journal 7
Assembly Programming Journal 7
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::. Dec 99-Feb 00
:::\_____\::::::::::. Issue 7
::::::::::::::::::::::.........................................................
A S S E M B L Y P R O G R A M M I N G J O U R N A L
http://asmjournal.freeservers.com
[email protected]
T A B L E O F C O N T E N T S
----------------------------------------------------------------------
Introduction...................................................mammon_
"Chaos Animation".......................................Laura.Fairhead
----------------------------------------------------------------------
+++++++++++++++++++Issue Challenge++++++++++++++++++
Dump the contents of the current console to a file
----------------------------------------------------------------------
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::..............................................INTRODUCTION
by mammon_
Well, yeah, there was; unfortunately once again real-world concerns interfered
with timely distribution. And, as usually happens with late issues, this one
is waaaaay oversized, almost 200K due to all the articles I crammed into it. I
didn't even get a chance to include my linux kernel modules article...
This issue seems to have a bit of a 'Hex-to-ASCII' bent to it, mostly from the
snippets but also from the conversion routines offered by Chris and Laura. In
addition, some 'fringe' asm has been supplied with Jan's Modula article, along
with an introduction to Alpha assembly language by Rudolph Seeman. Konstantin
Boldyshev, who helps maintain the linuxassembly.org site, continues the Unix
trend with an introduction to frame-buffer programming under linux.
The two leading articles are both quite large and offer a wealth of information
for the beginning and experienced asm programmer. Digital Alchemist has produced
a work on applying virus techniques to non-destructive applications, and S.
Sirajudeen has tackled the huge problem of creating a decent UI in console-mode
programs.
In this issue I have tried to leave the code comments as untouched as possible;
the coding styles of the authors vary quite widely, and each clearly demonstrates
the planning behind the program itself -- showing how the algorithm was
conceived before implementation. Stripping any of these examples of all but
comments will soon reveal the worksheet used by the coders to develop their
programs.
Finally, I have taken to formatting these issues in Vim under linux; to check
margins and pagination I have begun proofing them in Netscape and WordPerfect
[10 pt Courier, natch]; they should view fine in any web browser and in most
word processors; to those stuck with Notepad or Edit.com ... my apologies.
_m
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Extending DOS Executables
by Digital Alchemist
The reason behind this essay is to show how techniques first developed by virus
writers can be used for benevolent purposes. It is my opinion that all
knowledge is good and viral techniques are certainly no exception. I will lead
you through the development of a program called DOSGUARD which benignly
modifies DOS executables, both COM and EXE.
DESCRIPTION OF DOSGUARD
-----------------------
DOSGUARD is a DOS COM program which I developed in order to restrict access to
certain programs on my computer. DOSGUARD modifies all of the COM and EXE
files in the current directory, adding code to each one that requires the user
to correctly enter a password before running the original program.
DOSGUARD, while sufficient for this article, could use a little work in the
realm of user friendliness. More user feedback and a better way to specify
which files to be modified are needed. In addition, I have written a version
of DOSGUARD that uses simple xor encryption to improve security.
I'll go over each of these steps in a little more detail with code snippets
where necessary. The complete source code for DOSGUARD can be found at the
end of the article and at my web page. Hopefully, the comments will be enough
to explain any areas I don't discuss in detail.
Essentially, the way DOSGUARD modifies COM files is by inserting a jump at the
beginning of the file which goes straight to the password authentication code,
located at the end of the file. If the correct password is entered by the
user, then it will restore the 5 bytes that were overwritten by the jump and
the identification string and execute the program just like DOSGUARD was never
there.
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
The "call" pushes the current ip onto the stack, which is the actual address of
the label "GET_START." Subtract the compiled address from the actual one and
there's our delta offset.
The second problem is to make sure the first 5 bytes of the host are restored to
their original values before we return from our jump and execute the host.
All of this information is stored in the EXE Header. Here's a brief rundown of
what the header looks like:
Following the EXE header is the relocation pointer table, with a variable
amount of blank space between the header and the start of the table. The
relocation table is a table of offsets. These offsets are combined with
starting segment values calculated by DOS to point to a word in memory where
the final segment address is written. Essentially, the relocation pointer
table is DOS's way to handle the dynamic placement of segments into physical
memory. This isn't a problem with COM files because there is only one segment
and the program isn't aware of anything else. Following the relocation pointer
table is another variable amount of reserved space and finally the program
body.
To successfully add code to an EXE file requires careful manipulation of the EXE
header and relocation pointer table.
The easiest way to think about EXE modification is to imagine that we are
adding a complete COM program to the end of the file. Our code will occupy its
own segment located just after the host. This one segment will serve as a code,
data, and stack segment just like in a COM program. Instead of inserting a jump
to take us there, we will simply adjust the starting segment values in the EXE
header to point to our segment.
will equal the size of the file. The initial cs times 16 is the code entry
point, of course. You have to add the header size because it isn't loaded into
memory along with the rest of the code and data.
To find out if there is enough room, all you have to do is subtract the offset
of the relocation pointer table and the number of entries in the table from the
size of the header. The result is the amount of free space in the table. All
of this information can be found in the handy dandy EXE header. Of course, you
have to take into account the units that each of these values are stored in
(bytes, paragraphs, etc.)
Now, DOSGUARD calculates the amount which needs to be written. This code is in
the function called CALC_SIZE. When CALC_SIZE is finished, cx will hold the
number of pages and "lps" will hold the size of the last page since it probably
will not be a full 512 byte page.
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
mov cx, word ptr [exehead+2]
mov word ptr [lps], cx ;Copy Last Page Size into lps
mov cx, word ptr [exehead+4];Copy Num Pages into cx
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
Once you know the amount of code you have to move, you have to come up with a
way to simultaneously read and write from the same file without overwriting
data that hasn't been read yet. DOSGUARD's solution is to use a 16 byte
buffer. DOSGUARD's move loop reads 528 bytes and writes out 512 bytes with each
iteration. In other words, it reads 16 bytes ahead of where it is writing so
that it doesn't overwrite bytes before they're read. DOSGUARD has a number of
functions for reading and writing pages, reading and writing paragraphs, and
moving the file pointer around. It also has one function for moving the 16
bytes at the end of the 528 byte buffer in memory to the front. Well, I'll shut
up now and show you the code for the move loop.
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
When DOSGUARD gets to the last page, it finishes things off by reading the last
fraction of a page and then writing out those bytes plus the 16 bytes that were
left buffered from the last iteration of the move loop.
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
Oh yeah, there is one more condition that needs to be handled here. If the last
page was almost full(496 or more bytes), then adding 16 bytes to the file size
will overflow that page so you have to add a whole new page.
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
The first thing to is figure out what the starting segment values need to be.
The starting cs will simply be the original file size divided by 16 minus the
header size. The initial ip will be 0 because of Step 11. In DOSGUARD's case
the ss will be the same as the cs and the sp will point to an address 256 bytes
after the end of our code. 256 bytes is plenty of room for DOSGUARD's stack.
This next bit of code calculates the new file size, in pages of course.
;calculate new file size
mov dx, word ptr cs:[9Ch]
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
Now, we have to add two pointers to the table. The first points to "hosts,"
which is the stack segment of the original program. The second points to
"hostc+2," which holds the original program's code segment.
In order to actually restore control to the host, our code must restore ss and
sp to their original values. Then, it jumps to the original cs:ip.
Also, inserted code can't be dependent on absolute addresses for its data.
Therefore, DOSGUARD accesses all data by its offset from the end of the file.
CONCLUSION
----------
Hopefully, i've explained the techniques I used in developing DOSGUARD well
enough for you to develop your own binary modiying programs. As I mentioned at
the beginning of this article, DOSGUARD has a lot a room for improvement. If
you are interested then you should check out my web page and download the
source for ENCGUARD, a more secure version of DOSGUARD. A nice way to extend
DOSGUARD would be to improve on the encryption techniques used in ENCGUARD. If
I ever find the time I would like to write a Win32 version of DOSGUARD which
could safely modify the PE file format. If I ever do embark on such a task,
I'll be sure to let the readers of Assembly Programming Journal know about it.
REFERENCES
----------
"The Giant Black Book of Computer Viruses, 2nd edition" by Mark Ludwig
CONTACT INFORMATION
-------------------
email: [email protected]
web page: http://www4.ncsu.edu/~jjsimpso/index.html
Check out my web page for more information on my research into code
modification. Also, feel free to email me with ideas, corrections,
improvements, etc.
---------------------------BEGIN DOSGUARD.ASM----------------------------------
.model tiny
.code
ORG 100h
START:
jmp BEGINCODE ;Jump the identification string
DB 'CG'
BEGINCODE:
mov dx, offset filter1
call FIND_FILES
mov dx, offset filter2
call FIND_FILES
;-------------------------------------------------------------------------
;Procedure to find and then infect files
;-------------------------------------------------------------------------
FIND_FILES:
SLOOP:
jc DONE
mov ax, 3D02h ;Open file R/W
mov dx, 9Eh ;Filename, stored in DTA
int 21h
mov bx, ax ;Save file handle in bx
mov ax, 3F00h ;Read first 5 bytes from file
mov cx, 5
mov dx, offset obytes
int 21h
COM:
;Check to see if file is already infected
;if it is, then skip it
cmp word ptr [obytes + 3], 'GC'
je NO_INFECT
;If we made it this far then we know the file is safe to modify
call INFECT_COM
jmp NO_INFECT
EXE:
;Read the EXE Header
call READ_HEADER
jc NO_INFECT ;error reading file so skip it
EXEOK:
;Make sure Overlay Number is 0
cmp word ptr [exehead+26], 0
jnz NO_INFECT
call INFECT_EXE
NO_INFECT:
mov ax, 4F00h ;Find next file
int 21h
jmp SLOOP
DONE:
ret
;-------------------------------------------------------------------------
;Procedure to infect COM files
;-------------------------------------------------------------------------
INFECT_COM:
xor cx, cx ;cx = 0
xor dx, dx ;dx = 0
mov ax, 4202h ;Move file pointer to the end of file
int 21h
ret
;-------------------------------------------------------------------------
;Procedure to infect EXE files
;-------------------------------------------------------------------------
INFECT_EXE:
NOROOM:
;Not enough room in the relocation table so we are going to
;have to add a paragraph to the table. As a result, we must
;read in the whole file after the relocation table and write
;it back out one paragraph down in memory.
xor cx, cx ;Move the file pointer to the end of
mov dx, word ptr [exehead+24] ;the relocation pointer table.
mov ax, word ptr [exehead+6];size of relocation table in doubles
add ax, ax ;* 4 to get bytes
add ax, ax
add dx, ax ;add that to start of table
push dx
mov ax, 4200h
int 21h
pop dx
call CALC_SIZE
cmp cx, 1
je LASTPAGE
MOVELOOP:
mov dx, offset buffer + 16
call READ_PAGE
call DECFP_PAGE
call WRITE_PAGE
call MOVE_PARA
dec cx
cmp cx, 1
jne MOVELOOP
LASTPAGE:
sub word ptr [lps], 16
mov cx, word ptr [lps]
mov dx, offset buffer + 16
mov ah, 3Fh
int 21h
push cx
mov dx, cx
neg dx
mov cx, -1
mov ax, 4201h
int 21h
pop cx
add cx, 16
mov dx, offset buffer
mov ah, 40h
int 21h
ADDPAGE:
;Adjust the header to add a page if the 16 additional bytes run
;over to a new page.
inc word ptr [exehead+4]
mov ax, 512
sub ax, word ptr [exehead+2]
mov dx, 16
sub dx, ax
mov word ptr [exehead+2], dx
HAVEROOM:
mov ax, word ptr [exehead+14] ;save orig stack segment
mov [hosts], ax
mov ax, word ptr [exehead+16] ;save orig stack pointer
mov [hosts+2], ax
mov ax, word ptr [exehead+20] ;save orig ip
mov [hostc], ax
mov ax, word ptr [exehead+22] ;save orig cs
mov [hostc+2], ax
mov dx, word ptr cs:[9Ch] ;calculate new size file size
mov ax, word ptr cs:[9Ah]
add ax, offset ENDGUARD2 - offset EXEGUARD + 200h
adc dx, 0
mov cx, 200h
div cx
mov word ptr [exehead+4], ax
mov word ptr [exehead+2], dx
add word ptr [exehead+6], 2
ret ;Done!
;-------------------------------------------------------------------------
;Procedure to calculate the amount that needs to be written
;-------------------------------------------------------------------------
CALC_SIZE:
;dx holds the position in the file where we want to start reading.
;So, the amount to read in and write back out is equal to the size
;of the file minus dx.
cmp dx, word ptr [lps] ;If bytes to subtract are less than
jbe FINDLPS ;lps then just subtract them and exit
mov ax, dx
xor dx, dx
mov cx, 512
div cx ;ax = pages to subtract
mov cx, word ptr [exehead+4];dx = remainder to subtract from lps
sub cx, ax
cmp dx, word ptr [lps]
jbe FINDLPS
sub cx, 1
mov ax, dx
sub ax, word ptr [lps]
mov dx, 512
sub dx, ax
FINDLPS:
sub word ptr [lps], dx ;Subtract start position and leave
;Num Pages the same
ret
;-------------------------------------------------------------------------
;Procedure to read the EXE Header
;-------------------------------------------------------------------------
READ_HEADER:
xor cx, cx ;Move the file pointer back
xor dx, dx ;to the beginning of the file
mov ax, 4200h
int 21h
mov cx, 1Ch ;read exe header (28 bytes)
mov dx, offset exehead ;into buffer
mov ah, 3Fh
int 21h
;-------------------------------------------------------------------------
;Procedure to read a page
;-------------------------------------------------------------------------
READ_PAGE:
push ax
push cx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to read a paragraph
;-------------------------------------------------------------------------
READ_PARA:
push ax
push cx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to write a page
;-------------------------------------------------------------------------
WRITE_PAGE:
push ax
push cx
push dx
ret
;-------------------------------------------------------------------------
;Procedure to write a paragraph
;-------------------------------------------------------------------------
WRITE_PARA:
push ax
push cx
push dx
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a page
;-------------------------------------------------------------------------
DECFP_PAGE:
push ax
push cx
push dx
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move file pointer back a para
;-------------------------------------------------------------------------
DEC_PARA:
push ax
push cx
push dx
pop dx
pop cx
pop ax
ret
;-------------------------------------------------------------------------
;Procedure to move the paragraph buffer to the front
;-------------------------------------------------------------------------
MOVE_PARA:
push cx
pop cx
ret
;-------------------------------------------------------------------------
;Code to add to COM files
;-------------------------------------------------------------------------
COMGUARD:
call GET_START
GET_START:
pop bp
sub bp, offset GET_START
READLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je CHECKPASS
cmp al, 13 ;Quit loop when CR read
jne READLOOP
CHECKPASS:
lea di, [bp + guess] ;Setup for passwd checking loop
lea si, [bp +passwd] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
CHECKLOOP:
cmpsb ;Compare passwd with guess
jne FAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne CHECKLOOP ;Loop until you've read first 8
SUCCESS:
mov cx, 5
cld
lea si, [bp + obytes]
mov di, 100h
rep movsb
push 100h ;return from the jump to execute
ret ;the host program
FAIL:
mov ah, 9h ;DOS print string
lea dx, [bp + badpass] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
ENDGUARD:
;-------------------------------------------------------------------------
;Code to add to EXE files
;-------------------------------------------------------------------------
EXEGUARD:
push ax ;Save startup value in ax
push ds ;Save value of ds
mov ax, cs ;Put cs into ds and es
mov ds, ax
mov es, ax
mov bp, offset ENDGUARD2 - offset EXEGUARD
mov ax, [bp-4]
EREADLOOP:
mov ah, 7h ;Read without echo
int 21h
inc cx ;Count of characters entered
stosb ;Store guess for comparison later
cmp cx, 10 ;Limit guess to 10 chars including CR
je ECHECKPASS
cmp al, 13 ;Quit loop when CR read
jne EREADLOOP
ECHECKPASS:
lea di, [bp-20] ;Setup for passwd checking loop
lea si, [bp-28] ;Setup addresses for cmpsb
xor cx, cx ;Set counter to zero
cld ;Tell cmpsb to increment si and di
ECHECKLOOP:
cmpsb ;Compare passwd with guess
jne EFAIL ;Abort program if password is wrong
inc cx ;Increment counter
cmp cx, 8 ;Only check first 8 chars
jne ECHECKLOOP ;Loop until you've read first 8
ESUCCESS:
pop ds
mov ax, ds
mov es, ax
pop ax
cli
mov ss, word ptr cs:[bp-10]
mov sp, word ptr cs:[bp-8]
sti
xor cx, cx
xor dx, dx
xor bp, bp
xor si, si
xor di, di
lahf
xor ah, ah
sahf
EFAIL:
mov ah, 9h ;DOS print string
lea dx, [bp-46] ;Print bad password msg
int 21h
mov ax, 4C00h
int 21h
ENDGUARD2:
filter1 DB '*.com',0
filter2 DB '*.exe',0
bytes DB 0,0,0,'CG'
exehead DB 28 dup (0)
buffer DB 512 dup (0)
para DB 16 dup (0)
lps DW 0
END START
---------------------------END DOSGUARD.ASM------------------------------------
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Creating a User-Friendly Interface
by S Sirajudeen
As I say these things, you may become frustrated and decide to skip user
interface design. Still, in developing utilities or packages for commercial
purpose, a programmer will have to do these things to accomodate users. This
is why I present this article.
This article will focus on user friendly features in DOS text mode.
In DOS text mode, user friendly means features such as menus, message box,
dialog box, list box, text window, radio button, status bar, mouse support etc.
In this article, I will cover only an about message box and a dialog box.
However, knowledge of interrupts (for screen and mouse handling) is essential,
even for a C/C++ programmer, to incorporate user friendly features in a DOS
based program.
GETTING STARTED:
Before going on, some things must be cleared.
--------------------------------------
ASCII code Description
--------------------------------------
179 | Vertical bar
EXAMPLE 1:
First of all, we're going to put a zooming message box in our program. It
is an introduction to second example.
You may be seen that some utlities such as Norton Utilities display
zooming message box to alert users.
LOGIC:
Assume that displaying boxes which are larger than previously
displayed box, means enalarging/zooming the previously displayed box.
i) Zoom box by n rows
ii) Zoom box by n columns
iii) Zooming box for n times
;;///////////////////////////////////////////////////////////////////////;;
DATASEG ;; Initialize variables
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
nl EQU 0Dh,0Ah
label dialog_box_text
db nl
db nl,' +-------------------------+--------------------------------------+'
db nl,' | ::/ \::::::. | Program to Display a Message Box |'
db nl,' | :/___\:::::::. | |'
db nl,' | /| \::::::::. | Written By S.SIRAJUDEEN. |'
db nl,' | :| _/\:::::::::. | E-Mail: [email protected] |'
db nl,' | :| _|\ \::::::::::. | |'
db nl,' | :::\_____\::::::::::. | Published in ASMJOURNAL |'
db nl,' | ::::::::::::::::::::::. | Internet: asmjournal.freeservers.com |'
db nl,' | AsmJournal | |'
db nl,' +-------------------------+--------------------------------------+'
db nl,' | # If you have any comments or suggestions then please email me|'
db nl,' | at [email protected] |'
db nl,' +----------------------------------------------------------------+'
db nl,nl,nl,nl
count dw $-offset dialog_box_text
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
upper_x equ 08 ;; Upper left corner of the box to be zoomed
upper_y equ 37
;;//////////////////////////////////////////////////////////////////////;;
UDATASEG
DW 100H DUP (?)
MyStack LABEL WORD
pop bx
inc bx ;; Point to next character
loop far ptr cs:display_text
ENDM ;;End of macro
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
mov cx,0008h ;; Don't change! Calculate how many times to zoom.
zoom:
push cx ;; @@Display a window which is zooming.
call window
pop cx
loop zoom
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
@display count, dialog_box_text
ret
Message_box ENDP
;;---------------------------------------------------------------------;;
;;Display a horizontal shadow.
@window shadow_colour,shadow_vertical_left_x,shadow_vertical_left_y,
shadow_vertical_right_x,shadow_vertical_right_y
;;--------------------------------------------------------------------;;
;;Display a horizontal shadow.
@window shadow_colour,shadow_horizontal_left_x, shadow_horizontal_left_y,
shadow_horizontal_right_x,shadow_horizontal_right_y
;;--------------------------------------------------------------------;;
@delay
ret
Window ENDP
END
;;////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\;;
EXAMPLE 2:
Well, next we're going to put a DIALOG BOX in our program.
;;///////////////////////////////////////////////////////////////////////;;
DATASEG ;; Initialize variables
m_x dw 00
m_y dw 00
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
RED EQU 4fh ;; @Color values
CYAN EQU 3fh
BLACK EQU 0fh
BLUE EQU 1fh
WHITE EQU 7fh
box_height EQU 10
box_width EQU 46
upper_left_row db left_x
upper_left_col db left_y
label dialog_box_text
db '+--------------- USER COMMENT ---------------+' ;Dialog box. The variable
db '| |' ;dialog_box_text contains
db '| Written By S.Sirajudeen |' ;10 lines; width of each
db '| E-mail: [email protected] |' ;line is 46 characters.
db '| |'
db '| HAVE YOU ENJOYED THIS PROGRAM? |' ;NOTE:
db '| |' ;If you edit here, you
db '| Yes # No # |' ;should UPDATE the
db '| ####### ####### |' ;text_width and
db '+--------------------------------------------+' ;text_line_count.
count dw $-offset dialog_box_text
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
shadow EQU WHITE ;; color of button shadow
vert_shadow db 220
button db 'y' ;; @Flag to keep track of the button selection. If the value
;; is 'y', the YES button has selected; 'n' for the NO button.
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
label thank_you ;; Message to be displayed upon YES button has pressed
db 07,' Written By S.SIRAJUDEEN',nl
db '4/55,L.M.BUILDING,KUMARESAPURAM,KUTHAPAR(PO),TRICHY-620013,TAMILNADU,INDIA'
db nl,' Email: [email protected]'
db nl,nl,' Thank you! Good-bye!!'
thank_you_count dw $-thank_you
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
;; -------------+----------- When a key has pressed, it returns a code.
;; |Extended Keys| Scan Code | This code is called SCAN CODE.
;; |-------------+-----------| Alphanumeric keys, tab, space and escape
;; | Left Arrow | 75 | keys return one byte code. But Extended
;; | Right Arrow | 77 | keys return two bytes code. The first byte
;; | Up Arrow | 72 | always 0. The second is the actual scan code.
;; | Down Arrow | 80 | Arrow keys, Home, End, PageUp, Page Down,
;; -------------+----------- Insert, Delete, Function keys, Pause/break,
;; Scroll Lock & Print Screen are called EXTENDED
;; KEYs.
LEFT_ARROW equ 75 ;; Scan code of LEFT ARROW key is 75
RIGHT_ARROW equ 77 ;; ,, RIGHT ARROW keyis 77
TAB_KEY equ 9 ;; Scan code of TAB key is 9
ENTER_KEY equ 13 ;; ,, ENTER key is 13
;;//////////////////////////////////////////////////////////////////////;;
UDATASEG
DW 50H DUP (?)
MyStack LABEL WORD
pop bx
inc bx ;; Point to next character
loop far ptr cs:display_text
ENDM ;;End of macro
ENDM
@Yes MACRO
mov button, 'y' ;; DON'T CHANGE! ; Update flag
@window select, yes_x, yes_y, yes_x, yes_y+(yes_char_count-1)
@window unselect, no_x, no_y, no_x, no_y+(no_char_count-1)
@No MACRO
mov button, 'n' ;; DON'T CHANGE! ; Update flag
@window unselect,yes_x, yes_y, yes_x, yes_y+(yes_char_count-1)
@window select, no_x, no_y, no_x, no_y+(no_char_count-1)
@Cursor yes_x,yes_y
@Display yes_char_count, yes
@Cursor no_x,no_y
@Display no_char_count, no_button
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
@window BLACK, 00, 00, 24, 79 ;;@Clear screen
display_thank_u:
cmp button,'y' ;; Check whether YES button has pressed/clicked
jne display_suggestion
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_end:
mov ax,4C00h ;; Terminate the program.
int 21h
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
start:
@window box_background_color,left_x,left_y,right_x,right_y ;;Display a BOX
pop bx
inc bx
loop far ptr cs:display_text ;; INNER LOOP
pop cx
pop bx
push bx
@Cursor upper_left_row, upper_left_col ;; Move cursor to next line within
pop bx ;; dialog box
loop far ptr cs:next_line ;; OUTER LOOP
@button_shadow
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_yes:
@Yes ;; Select the YES button
cmp left_mouse_button,01
je _end_proc
jmp mouse_check
_no:
@No ;;Select the NO button
cmp left_mouse_button,01
je _end_proc
mouse_check:
cmp mouse, 'y' ;; Check whether mouse is available
jne key_check
mov left_mouse_button,bl
mov word ptr m_x,dx
mov word ptr m_y,cx
mouse_button:
and left_mouse_button, 01 ;; Check whether left mouse button has pressed
cmp left_mouse_button, 01
jne key_check
mouse_row:
mov mouse_x,0 ;; Mouse movement is converted into rows and columns
;; to calculate the position of mouse cursor
cmp word ptr m_x,00
je mouse_col
mov ax,word ptr m_x ;; In the text mode 3, to calculate the current ROW,
mov bl,8 ;; divide the position value for VERTICAL movement
div bl ;; by 8.
mov mouse_x, al
mouse_col:
mov mouse_y,0 ;; Mouse movement is converted into rows and columns
;; to calculate the position of mouse cursor
cmp word ptr m_y,00
je key_check
mov ax, word ptr m_y ;; In the text mode 3, to calculate the current COLUMN,
mov bl,8 ;; divide the position value for HORIZONTAL movement
div bl ;; by 8.
mov mouse_y, al
mouse_yes:
mov al, mouse_x
cmp al, yes_x ;; Check whether mouse has clicked anywhere on
jne mouse_no ;; the row where YES button is displayed
mouse_no:
mov al, mouse_x
cmp al, no_x ;; Check whether mouse has clicked anywhere on
jne key_check ;; the row where NO button is displayed
key_check:
mov ah,01 ;; @Check whether any character is in keyboard buffer
int 16h
jz mouse_check
mov ah,08 ;; @Receive character without echoing to screen
int 21h
cmp al, LEFT_ARROW ;; Check whether LEFT ARROW key has pressed
je _left
cmp al, RIGHT_ARROW ;; Check whether RIGHT ARROW key has pressed
je _right
jmp mouse_check
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;
_left:
cmp button,'y'
je _no
jmp _yes
_right:
cmp button,'y'
je _no
jmp _yes
;;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -;;
_end_proc:
RET
Dialog_box ENDP ;; End of procedure
Now, we have written a superb user friendly program. If you want to embed the
above examples in your work, you may have to heavily change these programs,
but the basic principles will be the same.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
ASM Building Blocks
by Laura Fairhead
Here are some simple but very powerful library routines, primarily
concerned with screen output. They all follow the same conventions:
* Routines preserve all registers that they are not specified to return.
* The direction flag (DF) should always be clear before calling.
All code is presented in MASM format. I do not use very many of the
functions of this assembler so it should be trivial to assemble these under
a different one. I do, however, use OPTION SCOPED, this means that labels
within a PROC block are local to that PROC block (a double colon suffixed
label is given global scope though).
First come the primitive routines. These are responsible for the actual
output and simply call DOS to do it. The name for this sort of thing is
called a 'wrapper' function. It does nothing in itself except afford a
particular interface to an application. If all your access to the OS is in
a small number of logical wrapper functions then porting your code to other
systems becomes a lot easier.
XCHG is in fact the real instruction hiding behind the psuedo-op NOP.
If you look at the opcode for a NOP, it is 090h, this is actually the
encoding for XCHG AX,AX, which since it has no effect on the machine state
whatsoever (except of course IP+=1) is ideally suited for this.
Not only is the putch method much cleaner and more flexible it is
also saving bytes! Of course the pay-back is that this method adds clocks.
However if you think about it the wasted clocks are meaningless really.
Sending characters one at a time to stdout is rather like spelling out
a dictate to your secretary letter-by-letter. In a case where you want
more MIPS you should be looking at your higher level algorithm and not
the output routine, an INT takes a vast amount of time anyway...
PUSH DX
XCHG DX,AX
MOV AH,2
INT 021h
XCHG DX,AX
POP DX
RET
putch ENDP
Not hot on speed this strlen, it was written to be compact. You can
if you wish write MUCH faster code than this. I believe X-Bios2 presented
something along these lines in a previous APJ. However, the most important
thing here is certainly not speed, and again if you wanted speed on string
handling so badly, you should really not use asciiz at all; it was never
designed for that.
PUSH AX
XOR CX,CX
DEC CX
lop: INC CX
LODSB
CMP AL,1
JNC lop
SBB SI,CX
POP AX
RET
strlen ENDP
PUSH CX
CALL NEAR PTR strlen
CALL NEAR PTR pstrcx
POP CX
RET
pstr ENDP
pstrcr ENDP
outcr ENDP
JCXZ don
PUSH CX
lop: CALL NEAR PTR putch
LOOP lop
POP CX
don: RET
pchn ENDP
PUSH AX
PUSH CX
CALL NEAR PTR pstr
MOV CH,0
XCHG CX,AX
CALL NEAR PTR strlen
SUB AX,CX
JNA SHORT don
XCHG CX,AX
MOV AL,020h
CALL NEAR PTR pchn
don: POP CX
POP AX
RET
pstrlcl ENDP
Note the use of JNA. If you look at the logic for the JNA branch
(not many people seem to do this) you find that it branches iff
CF=1 OR ZF=1, hence after the SUB if the result goes <=0
You may notice that all the routine names are <= 8 chars. The reason
for this being that you can save each one as a seperate file, giving it
the name of the routine. This allows easy reference but has a drawback
or two:
(i) you have to remember the dependencies when you INCLUDE them
(ii) you end up with a LOT of files
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Converting Strings to Numbers
by Chris Dragan
Many programs require user input, which is often numbers. For this purpose
there are library functions, like for example sscanf() in C. But in assembly
all has to be done by hand, even under Windows (with the exception of edit
controls - GetDlgItemInt() function).
10 decimal integer
10D decimal integer
1010B binary integer
AH hexadecimal integer (does not require leading zero)
0XA hexadecimal integer
$A hexadecimal integer
12Q octal integer
12O octal integer
10F float
10.0 float
10.0F float
1.0E+1F float
1.E+1 float
The string is required to have all letters (hex digits, number type
specifiers) uppercase. If a number is to contain lowercase letters, it has
to be converted before calling the function.
And here is the function. It was written (and tested) in TASM's ideal mode,
but it can be easily ported to MASM or NASM. The function preserves all
registers but eax, ecx and edx, which are used for return value.
; Is there anything ?
cmp esi, edi
ja __invalid
; Prepare
__decimal: mov [byte edi], 0
mov edi, esi
xor eax, eax
; Get a digit
__next_decimal: movzx ecx, [byte edi]
inc edi
xor edx, edx
; Next digit
add esp, 4
jmp __decimal_qword
; Handle overflow
__decimal_overflow: pop eax
jmp __invalid
; Get a digit
__get_hex: movzx ecx, [byte edi]
inc edi
; Prepare
__binary: mov [byte edi], 0
xor eax, eax
xor edx, edx
mov edi, esi
; Get a digit
__get_binary: movzx ecx, [byte edi]
inc edi
; Prepare
__octal: mov [byte edi], 0
xor eax, eax
xor edx, edx
mov edi, esi
; Get a digit
__get_octal: movzx ecx, [byte edi]
inc edi
__invalid: fninit
xor eax, eax
ret
; Get a digit
__get_integer: movzx ecx, [byte edi]
inc edi
; Get a digit
__get_fraction: movzx ecx, [byte edi]
inc edi
; E starts exponent
cmp ecx, 'E'
je __fraction_ready
; Get a digit
__get_exponent: movzx ecx, [byte edi]
inc edi
; Return float
__float_ready: chkfpu __invalid
fstp st(1)
mov eax, 3
ret
endp
And that is it. The function is not meant to work as fast possible and was
not optimized, but it does the task it has to do.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
List Scan Library Routine
by Laura Fairhead
========START OF CODE======================================================
;
;scaws- scan whitespace
;
;entry: DS:SI=string
; DF=0
;
;exit: SI=updated to first non-whitespace character
; AL=value of the character
;
;
;there is nothing to explain here but you might take note now
;that I always use the same label names in different PROC blocks,
;in MASM you can do this with OPTION SCOPED
;
lop: LODSB
CMP AL,020h
JZ lop
CMP AL,09h
JZ lop
DEC SI
RET
scaws ENDP
========END OF CODE========================================================
Here are some examples, all of these assume that we had set the
radix = 010h (by calling 'scanur' with AL=010h) :-
01 02 03 41 42 43
30 00 00 00 08 FE 01 00 02 00 00 00
06 9A 87 00 44 00 45 00 46 00
One last note is that the end of the list is the first invalid
character in the string, this not being an error of course since it is
the responsibilty of the controlling parser to decide this based on the
context; eg: DEBUG might check for a semicolon comment on the end of
the line, though as a matter of fact it doesn't. A premature ending (ie:
0 byte appearing inside the quotes of a string token) will abort with
error, thus;
========START OF CODE======================================================
;
;scalst- data list scan/convert routine
;
;entry: DS:SI=string
; ES:DI=store
; CX=#bytes size of store
; AL=unit size (1=byte,2=word,4=dword)
; DF=0
;
; "scanur" must have been called at least once previously
; in order to set the radix of scanned values
;
; !! entry parameters are not validated and invalid entry
; !! parameters will cause undefined behaviour
;
;exit: CF=1=>error (parse/overflow)
; CF=0=>okay, then:
; ZF=1=>no data scanned, ie: CX=0
; ZF=0=>data scanned
; SI=updated to the first invalid character
; DI=updated to the end of converted data + 1
; CX=#bytes converted data (invalid on overflow error)
;
;note: requires routines "scaws" and "scanu"
;
;
;initialise stack frame
;[BP-4] (dw) size mask
; =000000FFh for unit size 1
; =0000FFFFh for unit size 2
; =FFFFFFFFh for unit size 4
;[BP-6] (w) unit size
;[BP-8] (w) original data offset DI
;
;EAX is preserved and the main loop is entered
;
ENTER 8,0
PUSH EAX
CBW
MOV [BP-6],AX
NEG AL
AND AL,3
SHL AL,3
PUSH CX
XCHG CX,AX
OR EAX,-1
SHR EAX,CL
POP CX
MOV [BP-4],EAX
MOV [BP-8],DI
JMP SHORT inlop
;
;main loop head
; ignore any whitespace and skip the optional comma
;
lop: CALL NEAR PTR scaws
CMP BYTE PTR [SI],','
JNZ SHORT ko
INC SI
;
;main loop entry
; ignore any whitespace and if a value token is recognised
; write it to data store and continue loop
;
inlop: CALL NEAR PTR scaws
ko: CALL NEAR PTR scanu
JC SHORT don
JZ SHORT ko2
;
; check that the value is in range for the unit size, if not
; abort here with an error
;
CMP [BP-4],EAX
JC SHORT don
CALL NEAR PTR wracc
JMP lop
;
; no value was present so check for a string
;
ko2: CMP BYTE PTR [SI],022h
CLC
JNZ SHORT don
;
; get string into data store
;
INC SI
XOR EAX,EAX
;
; exit point for 'wracc' routine below, clean-up the stack
;
err0: POP EAX
;
; main exit point. the carry flag is preserved as this is used
; for both error and normal exits. the number of bytes stored
; is calculated into CX, the INC/DEC ensuring ZF=1 if this was zero
;
don: LAHF
MOV CX,DI
SUB CX,[BP-8]
SAHF
INC CX
DEC CX
;
; restore the only corrupted register and 'LEAVE'
;
POP EAX
LEAVE
RET
;
;wracc- write datum in accumalator to data store
; AL/AX/EAX is written to the data store depending on the unit size.
; throughout the routine DI is the offset into the data store and
; CX is the #bytes left in it. these are updated but if there are
; insufficient bytes remaining in the store we abort with error, taking
; care to clear the 4 bytes (AX + return address) off the stack first
;
wracc: PUSH AX
MOV AX,[BP-6]
SUB CX,AX
JC err0
CMP AL,2
POP AX
JZ SHORT ko0
JNS SHORT ko1
STOSB
RET
;
; note that 066h STOSW = STOSD
;
ko1: DB 066h
ko0: STOSW
RET
scalst ENDP
========END OF CODE========================================================
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Using the RTC
by Jan Verhoeven
Here are some routines to use the RTC/CMOS chip for serious timing. It's
an introductory tutorial, so you'll be given more than enough opportunity to
experiment with timing via this method.
I will describe the Dallas DS 1287 since this is the configuration which
is most common for many years now, and the majority of the features are
the same as for the other chips.
The DS 1287 has 64 storage locations, 14 of which are clock and control
registers and the remaining 50 are battery-backed general purpose RAM
cells. This is were the CMOS setup of your PC stores it's system setup
data.
address purpose
------- ---------------------------------------
0 current value of seconds
1 alarm setting for seconds
2 current value of minutes
3 alarm setting for minutes
4 current value of hours
5 alarm setting for hours
6 Day of the week [Sunday = 1]
7 Day of the month
8 month [0..12]
9 year of this century [0..99]
10 Control register A
11 Control register B
12 Control register C [read-only]
13 Control register D [read-only]
If you want to know the time of day, or any other date related data,
just select the RTC chip and request the contents of the desired
register.
In the PC, the RTC chip is hidden from the programmer. It can only be
accessed in an indirect way. The trick is to first select a register
location and then access that one register as follows:
bit function
--- ------------------------------------------------------------
7 UIP bit: Update In Progress. When there's a ONE in this flag
the timing registers are being updated and it is not safe to
read them. Better to wait until this flag is cleared.
This one bit is read-only!
0-3 RS0 - RS3: These are the four Rate Selector bits. They
determine how often the IRQ pin is activated. The following
table shows the meaning of the different values.
bit function
--- ------------------------------------------------------------
7 SET : If you determine to write a ONE in this bit position,
the clockregisters will not be updated anymore. Only when
this bit is ZERO, the clockregisters will be updated.
5 AIE : Alarm Interrupt Enable. When this bit is ONE, the IRQ
pin is activated when the alarm-time equals the actual time.
bit function
--- ------------------------------------------------------------
7 IRQF : If this bit is ONE, one of the actual interrupt
conditions was enabled and the interrupt condition was met.
If you need to change a timing value, you must always first disable
register updates, even if you make sure that the changes you make to the
timing registers will well fit in an RTC timeslot. This means:
In order to set the periodic interrupt rate, we use the following code:
This code is very straightforward. It relies on the fact that (in the
IBM PC) the contents of register A are always the same:
bit 7 = read-only
bits 4 - 6 = 010
bits 0 - 3 = rate selector
So, it can set the value of bits 4 - 7 in the calling code. It is not
good programming, since we should:
Normally IRQ8 is NOT enabled, so you will first have to settle that with
the PIC, which is far from easy to understand. I use the following code
to enable and disable the IRQ8 processing. Disabling this interrupt is
necessary after your program is unloaded from memory. If you don't do
this, the IRQ service routine vector might point to some random code or
data in the next program loaded (like Command.Com).
Easy, isn't it? It took some nights to figure this out, 'cause the Intel
databooks are not that clear. I was glad to find some NEC databooks
since these shed some more light. In general, for older chips, NEC is a
good choice of databooks. They used to second source 80x86 chips for
Intel and are still known for their innovations they put into their V20
and V30 chips. The V25, a vastly improved 8088, was contaminated by 8
full banks of 14 registers. Luckily Intel did not copy this. What would
a 386 have been with 250 GP registers?
Asserting IRQ8 will make the PC generate an INT 70h. So, we need to have
an INT 70h handler ready:
I hate the idea of having an INT routine that actually DOES things, but
which, for some obscure reason, cannot complete before the next INT
comes in. You'll be able to figure out what will happen in most cases.
If this routine sets a flag twice, I don't care too much. OK, I loose a
sample, but the program keeps running and it will still terminate when I
ask it to.
This routine:
The PIC needs an EOI to enable lower priority interrupts. And since
there are two PIC's in modern PC's, there also must be two EOI's.
And before going back to the OS of your choice, make sure there will be
no IRQ8's anymore coming this way:
In the big program these code fragments are from, I use two timer
interrupts:
- the RTC timer is used for trigger-timing. When the RTC has set the
right flag, the main program will sample the ADC and store the
result in a buffer for later processing.
mov ah, 0F
int 010 ; determine existing video mode
mov [VidMode], al ; store it
mov ax, 012
int 010 ; set 640 x 480 graphics mode
push es
mov ax, 0351C ; get old timervector
int 021
mov w [OldIRQ0], bx
mov w [OldIRQ0+2], es
mov dx, offset NewIRQ0
mov ax, 0251C
int 021 ; install new TIMER routine
mov ax, 03570
int 021
mov w [OldClock], bx
mov w [OldClock+2], es
mov dx, offset NewIRQ8
mov ax, 02570
int 021 ; install NewIRQ8 routine
call EnableNewIRQ8 ; and get it to work
pop es
mov ax, 0
int 033 ; init mouse
ShowMouse ; this is a macro....
call FillScreen
or [Flags], RfrshBar + RefrPara + Upd8Digs
call ShowDig
call BrScale
call Update
ret
So, on to the EXIT part of the software. Forget this, and the computer
will hang on random times afterwards....
That's all you need to know to get started. The RTC chip has some nice
other possibillities. It can be programmed to interrupt each second. Or
any other number of seconds. It is a truly versatile chip with many
timing functions directly available to systems level programmers.
Have fun exploiting the RTC chip, but be prepared to hit the reset
button now and then. Also, make a backup of the CMOS battery-backup RAM
onto a floppy disk! You'll have corrupted or erased these data before
you know it and it's always a bit of a shock if the system cannot even
find the C: drive anymore....
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Chaos Animation
by Laura Fairhead
To assemble this program you are going to require most of the library
routines I have so far presented here. You can consider this an example
in just how easy it is to write software in assembler if you continue
to build and refine a library system. The program probably took me about
half an hour of work and most of that was making myself satisfied with
the niceness of the code:-
;issue #5
INCLUDE NUCONV.ASM
;issue #6
INCLUDE SCANU.ASM
;issue #7
;scalst list scanner
INCLUDE SCAWS.ASM
Overview
~~~~~~~~
This is a simple but endearing graphical animation minature that
is based on the iterative function:-
x' = x*x + y + a
y' = b - x
Where a,b are constants, x,y are old coordinates and x',y' are the
new ones. All values are taken to be in [0,1). That is the operations
are all performed modulo 1.
mod1(x) = x - int(x)
This can all be done nicely within the bounds of 32-bit values,
simply view the binary point as being just before the MSbit.
We only really have 4 values to keep since x',y' are the next x,y.
The EQU's at the program end are defining offsets for uninitialised
data that lies in the primary code segment. Here we have kaa<->a, kab<->b,
kax<->x, kay<->y
;EAX=x
MOV EAX,DWORD PTR DS:[kax]
;EBX=b
MOV EBX,DWORD PTR DS:[kab]
;EBX=b-x =y'
SUB EBX,EAX
;ECX=y' for later use
MOV ECX,EBX
;EBX=y, y'->y (didn't I say XCHG is useful!)
XCHG EBX,DWORD PTR DS:[kay]
;EBX=y+a
ADD EBX,DWORD PTR DS:[kaa]
;EDX:EAX=x*x
;high dword is the first 32 b.p's....
MUL EAX
;EBX=x*x+y+a =x' (how much of the pattern is due to loss of accuracy here?)
ADD EBX,EDX
;x<-x'
MOV DWORD PTR DS:[kax],EBX
Reasonably efficient, and we come out with the x,y coordinate pair
also in EBX,ECX.
With mode 011h you have of course only the one plane. You've got
bytes from +00h to +04Fh on each row representing 8-bit pixel groups.
offset =x SHR 3
bit =x AND 3
Now of course it is plain to see that the offset is simply the result
of multiplication by 5 and then shift left 4. (unless you work always in
decimal, ala 050h=5*010h)
LEA SI,[EDX*4+EDX]
This puts DX*5 straight into SI. Thats about 5 operations all in one go:)
The routine keeps the x/y components apart because we want to plot
(x,y) (-x,y) (-x,-y) (x,-y). And as soon as they are put together for
one they need to be disassembled/reconstructed for the next.
MOV CL,BL
MOV AX,0180h
ROR AL,CL;ROL AH,CL
Here I am getting AL with the bit set that corresponds to the the pixel
on screen. AH is being set up the opposite way around. Think of the screen
as four quadrants:-
|
x+ | x-
y+ |
|
-----------+-------------
|
|
y- |
|
|
( SHR BX,3 )
XOR [SI+BX],AL
Now to reflect the point x-wise, so it goes to x-y+, you only need
to get the x offset = 04Fh-x. Well x86 lets you do powerful things,
we don't need to mess; just negate BX to get the -x and add the 04Fh
in as a displacement. Of course the bit offset gets negated as well,
which is exactly why we have the two opposite masks in AL/AH:-
Notes
~~~~~
During the program run you can press any key to set different values
for the chaos function. Press ESC to abort. On abortion a message will
give you 2 hex d-words, these are the random number seed that generated
the last pattern you were watching. To see it again simply record the
values and invoke the program with the values on the command line:-
(ESC abort)
(chaos pattern displayed is the same as the one broken out of)
The code is left undelayed and as such it may run too fast on a fast
machine. The optimum speed is for it to be only slighty over-fast. If
you want to achieve this you should add some sort of delay loop in. Alt-
ernatively just get out your old 386 and give it some work to do.
========START OF CODE======================================================
OPTION SCOPED
OPTION SEGMENT:USE16
.486
ASSUME NOTHING
ORG 0100h
lop2:
lop0:
;iterate x,y
; x'=x*x+y+a
; y'=b-x
MOV EAX,DWORD PTR DS:[kax]
MOV EBX,DWORD PTR DS:[kab]
SUB EBX,EAX
MOV ECX,EBX
XCHG EBX,DWORD PTR DS:[kay]
ADD EBX,DWORD PTR DS:[kaa]
MUL EAX
ADD EBX,EDX
MOV DWORD PTR DS:[kax],EBX
MOV EDX,ECX
SHR ECX,4
SUB EDX,ECX
SHR EDX,23
;do point
; [kaera] is -1 on and after the store had become full for the first
; time
MOV DI,WORD PTR DS:[kaoff]
TEST BYTE PTR DS:[kaera],-1
JZ SHORT ko3
;unplot trail end point
PUSH BX
PUSH DX
MOV BX,[DI]
MOV DX,[DI+2]
CALL NEAR PTR plo4
POP DX
POP BX
;user
; ESC aborts, any key sets a new function going
MOV AH,0Bh
INT 021h
CMP AL,0
JZ lop0
MOV AH,7
INT 021h
CMP AL,01Bh
JNZ lop2
;screen mode is not put back to 02h you may wish to add
;a MOV AX,2;INT 010h here however I left it out because I
;see way too much of that mode
;program termination
terminat0:
MOV AL,0
terminat:
MOV AH,04Ch
INT 021h
;error aborts
erripa:
MOV SI,OFFSET terripa
MOV AL,2
JMP SHORT err
errmem:
MOV SI,OFFSET terrmem
MOV AL,1
err:
PUSH SI
MOV SI,OFFSET terr
CALL NEAR PTR pstr
POP SI
CALL NEAR PTR pstrcr
JMP terminat
kode ENDP
;plo4- 4-way plot routine for mode 011h
;
; plots 4 reflections of a single point on the mode 011h
; screen these are (x,y) (-x,y) (-x,-y) (x,-y)
;
; plots using XOR
;
;entry: BX,DX=x,y coordinates
;
;exit: SI,CL,AX,BX destroyed
PUSH DS
plo4 ENDP
;if you don't like my fade grey background you can delete this and
;the line that invokes it. However this is also the next library
;routine, so do cut/paste it into a file it will be used in future
;articles.
spal ENDP
;library routines
INCLUDE RAND.ASM
INCLUDE SCAWS.ASM
INCLUDE SCANU.ASM
INCLUDE NUCONV.ASM
INCLUDE PSTR.ASM
INCLUDE PSTRCR.ASM
INCLUDE PSTRCX.ASM
INCLUDE OUTCR.ASM
INCLUDE STRLEN.ASM
INCLUDE PUTCH.ASM
;data
cseg ENDS
========END OF CODE========================================================
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Inline Assembler With Modula
by Jan Verhoeven
For this Modula-2 compiler I used my VGA routines (see previous issues) and
some in-line assembly to give this compiler a way to do graphics modes.
I uploaded the full sources to SimTel some months (or years?) ago, so if you
would like to have a detailed look at it, go there and look for it.
---------------------------------------------------------------------------
IMPLEMENTATION MODULE VgaLib;
BEGIN
ASM
MOV AX, x
MOV CX, y
AND CX, 15 (* Mask off lower nybble *)
JCXZ ok (* Get out if no shift. *)
SHL AX, CL
ok: MOV result, AX (* Store result. *)
END;
RETURN result;
END SHL;
---------------------------------------------------------------------------
The only "drawback" is that the in-line code must be 8088 style. So you won't
be eable to use MMX instructions, but almost no-one ever needs those.
FST Modula-2 offers direct access to (values of) variables. Neat. Makes the
in-line feature very convenient to use.
---------------------------------------------------------------------------
BEGIN
ASM
MOV DX, 03C4H (* VGA controller port *)
MOV AH, Colour
MOV AL, 2
OUT DX, AX
END;
END SetColour;
---------------------------------------------------------------------------
Compare the following routine with the one I entered for the VGA-12h code in
A86 assembly language format. There's some Modula-2 overhead, but the actual
plotting is done in ASM, for speed-reasons.
---------------------------------------------------------------------------
PROCEDURE Plot (VAR InWin : WinData); (* Plot point on CurX, CurY. *)
VAR x, y : CARDINAL;
BEGIN
x := InWin.CurX + InWin.TopX;
y := InWin.CurY + InWin.TopY;
ASM
MOV AX, 0A000H
MOV ES, AX (* Set up segment register *)
MOV CX, x
AND CX, 7 (* Which bit to plot? *)
MOV AH, 80H
SHR AH, CL (* Compose plotting mask *)
MOV AL, 8
MOV DX, 03CEH
OUT DX, AX (* Set plottingmask *)
MOV AX, y (* Calculate offset in Video RAM *)
MOV BX, AX
ADD AX, AX
ADD AX, AX
ADD AX, BX (* AX := 5 * Y *)
MOV CL, 4
SHL AX, CL (* AX := 16 * 5 * Y *)
MOV BX, x
SHR BX, 1
SHR BX, 1
SHR BX, 1
ADD BX, AX (* plus X / 8 *)
MOV AL, ES:[BX]
MOV AL, 0FFH
MOV ES:[BX], AL (* and plot it *)
END;
END Plot;
BEGIN
IF Flag THEN (* Flag = TRUE => Plot, else UnPlot *)
Val := 0FFX;
ELSE
Val := 0X;
END;
IF InWin.DeltaX < 18 THEN
FOR Index := 0 TO InWin.DeltaX DO (* For short lines *)
Plot (InWin);
INC (InWin.CurX);
END;
ELSE
x := InWin.TopX + InWin.CurX; (* For long lines *)
y := InWin.TopY + InWin.CurY;
dx := InWin.DeltaX;
ASM
MOV AX, 0A000H
MOV ES, AX (* Set up segment register *)
MOV CX, x
AND CX, 7
MOV BX, 8
SUB BX, CX
MOV AL, 0FFH
SHR AL, CL
MOV Emask, AL (* compose plotting mask *)
MOV CX, dx
SUB CX, BX
MOV AX, CX
AND AX, 7
PUSH AX (* Save L-val *)
SUB CX, AX
SHR CX, 1
SHR CX, 1
SHR CX, 1
MOV Kval, CX
MOV AL, 0
POP CX (* retrieve L-val *)
JCXZ L0
MOV AL, 080H
L0: DEC CX
SAR AL, CL
MOV Lmask, AL
MOV AX, y (* Calculate offset in Video RAM *)
MOV BX, AX
ADD AX, AX
ADD AX, AX
ADD AX, BX (* AX := 5 * Y *)
MOV CL, 4
SHL AX, CL (* AX := 16 * 5 * Y *)
MOV BX, x
SHR BX, 1
SHR BX, 1
SHR BX, 1
ADD BX, AX (* plus X / 8 *)
INC BX
MOV CX, Kval
JCXZ L2
MOV AX, 0FF08H
OUT DX, AX
MOV AH, Val
L1: MOV AL, ES:[BX]
MOV ES:[BX], AH
INC BX
LOOP L1
L2: MOV AH, Lmask
MOV AL, 8
OUT DX, AX
MOV AL, ES:[BX]
MOV AL, Val
MOV ES:[BX], AL
END;
INC (InWin.CurX, dx);
END;
END DrawH;
BEGIN
IF Letter = 0AX THEN
INC (InWin.CurY, 16); (* Process LF *)
RETURN;
END;
IF Letter = 0DX THEN
InWin.CurX := InWin.Indent; (* Process CR *)
RETURN;
END;
IF InWin.CurX >= InWin.Width - ChrWid THEN
InWin.CurX := InWin.Indent;
INC (InWin.CurY, 16);
END;
xpos := InWin.CurX + InWin.TopX;
ypos := InWin.CurY + InWin.TopY;
VGApos := 80 * ypos + SHR (xpos, 3);
VGAseg := 0A000H;
MapOfs := ORD (Letter) * 16;
ASM
PUSH ES (* save ES *)
MOV CX, xpos
AND CX, 7
MOV Cval, CL (* nr of bits "off center" *)
MOV BX, 0FF00H
SHR BX, CL
MOV Pmask, BX (* mask to use for left and right halves *)
MOV AX, BX
MOV AL, 8
MOV DX, 03CEH
OUT DX, AX (* set plotting mask for left part *)
MOV CX, 16
MOV BX, VGApos
LES SI, BitMap (* here are the pixels that make the tokens *)
ADD SI, MapOfs
L0: PUSH CX
LES AX, BitMap (* load ES, AX is just scrap *)
MOV AH, ES:[SI] (* load pattern *)
MOV CL, Cval
SHR AX, CL (* compose left half *)
MOV ES, VGAseg
MOV AL, ES:[BX]
MOV ES:[BX], AH (* and "print" it *)
ADD BX, 80 (* point to next row *)
INC SI (* and next pixel pattern *)
POP CX
LOOP L0 (* repeat until done *)
MOV AX, Pmask
CMP AL, 0 (* if Cval = 0 => perfect allignment *)
JE ex (* skip second half *)
XCHG AH, AL (* else repeat the story once more *)
MOV AL, 8
OUT DX, AX (* set up mask for right half *)
MOV CX, 16
SUB BX, 1279 (* 16 x 80 - 1 *)
SUB SI, CX
L1: PUSH CX
LES AX, BitMap
MOV AH, ES:[SI]
MOV AL, 0
MOV CL, Cval
SHR AX, CL
MOV ES, VGAseg
MOV AH, ES:[BX]
MOV ES:[BX], AL
ADD BX, 80
INC SI
POP CX
LOOP L1
ex: POP ES
END;
INC (InWin.CurX, ChrWid); (* point to next printing position *)
END PlotChar;
---------------------------------------------------------------------------
And here is the promised solution for the "make a box-drawing routine" problem
of the previous issue. OK, the solution is in Modula-2, but since this is such
a clear to understand language it will be no big deal to port this code to
assembly language format.
---------------------------------------------------------------------------
PROCEDURE MakeBox (InWin : WinData);
(* Make a box on screen starting at (TopX, TopY). *)
BEGIN
InWin.CurX := 0;
InWin.CurY := 0; (* Make sure pointers are correct *)
InWin.DeltaX := InWin.Width - 1;
InWin.DeltaY := InWin.Height - 1; (* setup parameters for drawing lines
*)
SetColour (InWin.BoxCol);
DrawH (InWin, TRUE); (* draw horizontal line *)
DrawV (InWin); (* draw vertical line *)
InWin.CurX := 0;
InWin.CurY := 1; (* adjust coordinates *)
DrawV (InWin); (* draw last vertical line *)
DEC (InWin.CurY);
INC (InWin.CurX); (* adjust coordinates once more *)
DrawH (InWin, TRUE); (* draw final line *)
END MakeBox;
END VgaLib.
---------------------------------------------------------------------------
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................FEATURE.ARTICLE
Assembly on the Alpha Platform
by Rudolf Seemann
Introduction
------------
The heart of the alpha architecture is a 64-bit RISC processor with 32 integer
($0 to $31) and 32 floating point registers ($f0 to $f31). Its operation codes
can be classified by the number of its operands:
class opcode
operate opcode Ra,Rb,Rc # Ra operation Rb -> Rc
opcode Ra,number,Rc # Ra operation number (0-255) -> Rc
memory opcode Ra,Disp(Rb) # load/store contents saved in memory address
# Rb + offset Disp in register Ra
branch opcode Ra,label # branch if Ra = true to label
PAL opcode number # opcodes for the operating system
Data Types are specified by suffixes (like q for quadword, l for longword).
Most integer operations only know these two suffixes. Floating point operations
know both: s and t.
If you want to use 64-bit numbers in the c-programming language (gcc), use
(long) or (long int). (int) is 32 bits long.
The following example was tested on an SX164 with SuSE Alpha Linux 6.3 (Kernel
2.2.13).
The Example
-----------
My c-program calls the assembler function div which divides the first argument
given to it by the second one. The arguments will be put in the integer
registers $16 and $17 by convention. So all we have to do is to divide register
$16 by $17. The alpha does not know any division for integer. There is a pseudo-
opcode for integer-division but I will show how to convert an integer to a
floating point number, do the division in the floating point registers and
convert it back to integer. Finally the result will be put by convention in
register $0 where the c-program expects it to be.
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................WIN32.ASSEMBLY.PROGRAMMING
Direct Draw Examples
by X-Calibre
As a follow-up to the Direct Draw article in APJ#5, here are two complete
DirectDraw sample programs. The first uses an 8-bit palette, while the
second uses a 32-bit (truecolor) palette. To compile these, you will need to
obtain Ddraw.inc ( http://asmjournal.freeservers.com/files/Ddraw.inc.html )
for the necessary DirectDraw definitions.
;Ddplasma8.asm_________________________________________________________________
;---------------------------------------;
; DDRAW Plasma Demo ;
; ;
; Author : X-Calibre ;
; ASM version : Ewald Snel ;
; Copyright (C) 1999, Diamond Crew ;
; ;
; http://here.is/diamond/ ;
;---------------------------------------;
INCLUDE \masm32\include\windows.inc
; -----------------------------------
; Note that the following is the
; include file written by Ewald Snel.
; -----------------------------------
INCLUDE \masm32\include\ddraw.inc
INCLUDE \masm32\include\gdi32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\user32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\ddraw.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.DATA?
.DATA
phaseA dd 0
phaseB dd 0
factor1 EQU -2
factor2 EQU -1
factor3 EQU 1
factor4 EQU -2
red dd 500.0
green dd 320.0
blue dd 372.0
scale1 dd 2.0
scale2 dd 128.0
scale3 dd 256.0
scale4 dd 127.0
.CODE
start:
;-----------------------------------------------------------;
; Calculate Next Plasma Frame ;
;-----------------------------------------------------------;
nextFrame PROC
push ebx
push esi
push edi
@@pixel:
and esi , 0ffH
and edx , 0ffH
mov eax , [table][4*esi]
mov ebx , [table][4*edx][256*4]
add eax , ebx
add esi , factor3
shr eax , 1
inc edi
add edx , factor4
dec ecx
mov [edi][-1] , al
jnz @@pixel
pop edi
pop ecx
add edi , [ddsd.lPitch] ; inc. display position
dec ecx
jnz @@scanline
pop edi
pop esi
pop ebx
ret
nextFrame ENDP
;-----------------------------------------------------------;
; Initalize Plasma Tables ;
;-----------------------------------------------------------;
initPlasma PROC
mov [@@i] , 0
.WHILE @@i < 256
fldpi
fimul DWORD PTR [@@i]
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fsin
fmul REAL4 PTR [scale4]
fadd REAL4 PTR [scale2]
fistp DWORD PTR [table][4*edx]
fldpi
fimul DWORD PTR [@@i]
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fcos
fmul REAL4 PTR [scale2]
fadd REAL4 PTR [scale2]
fldpi
fmulp st(1), st
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fsin
fmul REAL4 PTR [scale4]
fadd REAL4 PTR [scale2]
fistp DWORD PTR [table][4*edx][4*256]
bswap eax
shr eax, 8
mov [palette][4*edx] , eax
inc [@@i]
.ENDW
; Set palette
DDINVOKE CreatePalette, lpDD, DDPCAPS_8BIT or DDPCAPS_ALLOW256,
ADDR palette, ADDR lpDDPalette, NULL
.IF eax != DD_OK
FATAL "Couldn't create palette"
.ENDIF
ret
initPlasma ENDP
;-----------------------------------------------------------;
; WinMain ( entry point ) ;
;-----------------------------------------------------------;
INVOKE CreateWindowEx, 0,
ADDR szClassName,
ADDR szDisplayName,
WS_POPUP,
0, 0, ddwidth, ddheight,
NULL, NULL,
hInst, NULL
mov [hWnd] , eax
; Initialize display
call initPlasma
.IF eax != 0
.IF msg.message == WM_QUIT
INVOKE PostQuitMessage, msg.wParam
.BREAK
.ELSE
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDIF
.ELSE
INVOKE GetFocus
.WHILE 1
DDSINVOKE mLock, lpDDSPrimary, NULL, ADDR ddsd, DDLOCK_WAIT,
NULL
call nextFrame
.ENDIF
LRETURN msg.wParam
WinMain ENDP
;-----------------------------------------------------------;
; Window Proc ( handle events ) ;
;-----------------------------------------------------------;
ret
WndProc ENDP
END start
;End_Ddplasma8.asm_____________________________________________________________
;Ddplasma32.asm________________________________________________________________
;---------------------------------------;
; DDRAW Plasma Demo ;
; ;
; Author : X-Calibre ;
; ASM version : Ewald Snel ;
; Copyright (C) 1999, Diamond Crew ;
; ;
; http://here.is/diamond/ ;
;---------------------------------------;
;-----------------------------------------------------------;
; WIN32ASM / DDRAW PLASMA DEMO ;
;-----------------------------------------------------------;
INCLUDE \masm32\include\windows.inc
; -----------------------------------
; Note that the following is the
; include file written by Ewald Snel.
; -----------------------------------
INCLUDE .\ddraw.inc
INCLUDE \masm32\include\gdi32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\user32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\ddraw.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.DATA
phaseA dd 0
phaseB dd 0
factor1 EQU -2
factor2 EQU -1
factor3 EQU 1
factor4 EQU -2
red dd 500.0
green dd 320.0
blue dd 372.0
scale1 dd 2.0
scale2 dd 128.0
scale3 dd 256.0
scale4 dd 127.0
.CODE
start:
;-----------------------------------------------------------;
; Calculate Next Plasma Frame ;
;-----------------------------------------------------------;
nextFrame PROC
push ebx
push esi
push edi
@@scanline:
push ecx
push edi
@@pixel:
and esi , 0ffH
and edx , 0ffH
mov eax , [table][4*esi]
mov ebx , [table][4*edx][256*4]
add eax , ebx
add esi , factor3
shr eax , 1
add edx , factor4
and eax , 0ffH
add edi , 4
mov eax , [palette][4*eax]
dec ecx
mov [edi][-4] , eax
jnz @@pixel
pop edi
pop ecx
add edi , [ddsd.lPitch] ; inc. display position
dec ecx
jnz @@scanline
pop edi
pop esi
pop ebx
ret
nextFrame ENDP
;-----------------------------------------------------------;
; Initalize Plasma Tables ;
;-----------------------------------------------------------;
initPlasma PROC
mov [@@i] , 0
fldpi
fimul DWORD PTR [@@i]
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fsin
fmul REAL4 PTR [scale4]
fadd REAL4 PTR [scale2]
fistp DWORD PTR [table][4*edx]
fldpi
fimul DWORD PTR [@@i]
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fcos
fmul REAL4 PTR [scale2]
fadd REAL4 PTR [scale2]
fldpi
fmulp st(1), st
fmul REAL4 PTR [scale1]
fdiv REAL4 PTR [scale3]
fsin
fmul REAL4 PTR [scale4]
fadd REAL4 PTR [scale2]
fistp DWORD PTR [table][4*edx][4*256]
.ENDW
ret
initPlasma ENDP
;-----------------------------------------------------------;
; WinMain ( entry point ) ;
;-----------------------------------------------------------;
INVOKE CreateWindowEx, 0,
ADDR szClassName,
ADDR szDisplayName,
WS_POPUP,
0, 0, ddwidth, ddheight,
NULL, NULL,
hInst, NULL
mov [hWnd] , eax
; Initialize display
call initPlasma
.WHILE 1
.IF eax != 0
.IF msg.message == WM_QUIT
INVOKE PostQuitMessage, msg.wParam
.BREAK
.ELSE
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDIF
.ELSE
INVOKE GetFocus
.WHILE 1
DDSINVOKE mLock, lpDDSPrimary, NULL, ADDR ddsd, DDLOCK_WAIT,
NULL
call nextFrame
.ENDIF
.ENDIF
.ENDW
.IF lpDD != NULL
.ENDIF
LRETURN msg.wParam
WinMain ENDP
;-----------------------------------------------------------;
; Window Proc ( handle events ) ;
;-----------------------------------------------------------;
ret
WndProc ENDP
END start
;End_Ddplasma32.asm____________________________________________________________
I had mail problems last time... I don't think the example program from
the DDRAW tut ever reached you... and now you were looking for a Windows
article for issue #6... Maybe you can put the example in there... It's
Win32, and it would also double as a sequel to the article of issue #5
:)
Well, there's 2 examples actually... They look the same on screen, but 1
displays how to use 8 bit palette mode (like good old mode 13h), where
the other shows 32 bit truecolor mode...
I also included the original DDRAW.INC, so people can assemble the
sources themselves...
I hope this time it reaches you, and that I could have been of help to
you,
X-Calibre
WINDOWS
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::............................................THE.UNIX.WORLD
Enter fbcon
by Konstantin Boldyshev
Many of Linux users have heard something about fbcon. It is becoming more
and more popular, mostly because of capability of getting graphics on
usual terminal without X. How to use graphic capabilities of fbcon?
The /dev/fb# devices represent frame buffer devices; they allow the frame
buffer of a video card to be read and written to by a user, and allow a
programmer to access the video hardware [and, more importantly, the video
memory] through ioctls and memory mapping.
I've taken one of my old DOS intros made in tasm, and rewritten it for nasm
and Linux/fbcon. At 408 bytes, This intro is the smallest implementation of
linear transformation with recursion (AFAIK).
Leaves.asm runs for about a minute and a half (depends on machine), and is
interruptible at any time with ^C. If everything is ok you should see two
branches of green leaves, and kinda wind blowing on them. It MUST be run only
in 640x480x256 mode (vga=0x301 in lilo.conf). You will see garbage or incorrect
colors in other modes.
Warning! Intro assumes that everything is ok with the system (/dev/fb0 exists,
can be opened and mmap()ed, correct video mode is set, and so on). So, if you
ain't root, check permissions on /dev/fb0 first, or you will not see anything.
The source is quite portable, you only need to implement putpixel() and initial-
ization part for your OS. To get the basic idea across, here is the fbcon
implementation in C:
//==========================================================================
// leaves.c : C implementation using /dev/fb0
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define xc MaxX/2
#define yc MaxY/2
#define xmin0 100
#define xmax0 -xmin0
#define ymin0 xmin0
#define ymax0 -ymin0
#define colornum 8
int h;
byte *p;
dword f=MaxY/(ymax0-ymin0)*3/2;
dword x1coef=MaxX-MaxY*4/9-yc;
dword y1coef=MaxY/4+xc;
dword x2coef=MaxY*4/9+yc;
dword x0=110;
dword a=0.7;
dword b=0.2;
dword c=0.5;
dword d=0.3;
if (n>0)
{
y1=f*x+y1coef;
putpixel(x1coef-f*y,y1,ColorTable[color]);
putpixel(f*y+x2coef,y1,ColorTable[color]);
if (++color>colornum-1) color=0;
}
}
int main(void)
{
int i;
p=mmap(0,VMEM_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,open("/dev/fb0",O_RDWR),0);
leaves(0,0,28);
munmap(p,VMEM_SIZE);
close(h);
}
//--------------------------------------------------------------------------EOF
Here is the asm source. It is quite short and self-explaining :) Well, actually
the source is badly optimized for size, contains some Linux-specific tricks,
and can be hard to understand. Please refer to the C source for areas that need
clarification
NOTE: The following source was taken from asmutils and requires asmutils
macros (*.inc), available from http://linuxassembly.org;
you can also download binary there (in samples archive)
To compile leaves.asm:
$ nasm -f elf leaves.asm
$ ld -s -o leaves leaves.o
;==========================================================================
;Copyright (C) 1999 Konstantin Boldyshev <[email protected]>
;
;leaves - fbcon intro in 408 bytes
;
;Ah, /if haven't guessed yet/ license is GPL, so enjoy! :)
%include "system.inc"
CODESEG
;al - color
putpixel:
push edx
lea edx,[ebx+ebx*4] ;computing offset..
shl edx,byte 7 ;multiply on 640
add edx,[esp+8] ;
mov [edx+esi],al ;write to frame buffer
pop edx
_return:
ret
; recursive function itself
leaves:
mov ecx,[esp+12]
test cl,cl
jz _return
mov [esp-13],cl
mov eax,[edi]
push ecx
sub esp,byte 8
mov edx,esp
.rec:
fld dword [ebp+4] ;[b]
fld dword [ebp] ;[a]
fld st1
fld st1
fxch
fmul dword [edx+16]
fxch
fmul dword [edx+20]
fsubp st1
fstp dword [edx-8]
fxch
fmulp st2
fxch st2
fmul dword [edx+32]
fsubp st1
faddp st1
push ecx
sub esp,byte 8
fstp dword [esp]
call leaves
add esp,byte 12*2+8
pop ecx
.return:
ret
;------------------------------------- main()
START:
;prepare structure for mmap on the stack
mov edi,VMEM_SIZE
mov esi,esp
mov [esi-16],edi ;.len
mov [esi-12],byte PROT_READ|PROT_WRITE ;.prot
mov [esi-8],byte MAP_SHARED ;.flags
mov [esi],edx ;.offset
;init fb
mov ebp,Params
lea ebx,[ebp+0x2C] ;fb-Params
sys_open EMPTY,O_RDWR
mov esi,eax
;clear screen
mov ecx,edi
mov edi,esi
xor eax,eax
rep stosb
;leaves
lea edi,[ebp+0x24] ;ColorBegin-Params
push byte 28 ;recursion depth
push eax
push eax
call leaves
;close fb
sys_munmap esi,VMEM_SIZE
sys_close [mm.fd]
exit:
sys_exit
;----------------------------Parameters
Params:
a dd 0.7
b dd 0.2
c dd 0.5
d dd 0.3
f dd 0xc0400000 ;MaxY/(ymax0-ymin0)*3/2
x1coef dd 0x433b0000 ;MaxX-MaxY*4/9-yc
y1coef dd 0x43dc0000 ;MaxY/4+xc
x2coef dd 0x43e28000 ;MaxY*4/9+yc
x0 dd 112.0
ColorBegin:
db 0,0,2,0,0,2,10,2
ColorEnd:
fb db "/dev/fb0";,NULL
END
;===========================================================================EOF
More information on the frame buffer device can be found in the Linux kernel
documentation [ usually /usr/src/linux/Documentation ] files framebuffer.txt,
internals.txt, matroxfb.txt, tgafb.txt, and vesafb.txt. The /dev/fbcon# ioctls
are defined in /usr/include/linux/fb.h .
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::................................ASSEMBLY.LANGUAGE.SNIPPETS
TOHEX
by Ronald
Hex2ASCII
by cpuburn
;Summary: Converts
;Compatibility: K7
;Notes: This
; While doing some light reading of the AMD K7 Athlon Optimization
;Manual, I came across one of the neatest hex-to-ASCII converters
;I've ever seen:
MMX ltostr
by Cecchinel Stephan
;Summary: Convert long [dword] value to an ASCII string
;Compatibility: MMX
;Notes: Converts a number in EAX to an 8 bytes hexadecimal string
; at [edi]
; 14 clocks on a Celeron-333
Sum1: dd 0x30303030, 0x30303030
Mask1: dd 0x0f0f0f0f, 0x0f0f0f0f
Comp1: dd 0x09090909, 0x09090909
Hex32:
bswap eax
movq mm3,[Sum1]
movq mm4,[Comp1]
movq mm2,[Mask1]
movq mm5,mm3
psubb mm5,mm4
movd mm0,eax
movq mm1,mm0
psrlq mm0,4
pand mm0,mm2
pand mm1,mm2
punpcklbw mm0,mm1
movq mm1,mm0
pcmpgtb mm0,mm4
pand mm0,mm5
paddb mm1,mm3
paddb mm1,mm0
movq [edi],mm1
ret
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::...........................................ISSUE.CHALLENGE
by Laura Fairhead
Challenge
~~~~~~~~~
Write a program that takes a snapshot of a text screen and writes it
to a file. It should work in any text mode and lines should be terminated
with newlines in the file so that it can easily be viewed in a standard
editor. ( 04Dh = 77 bytes )
Solution
~~~~~~~~
If you want to assemble this just remember FS = 064h, as MASM can't cope
with legal x86 code. Then just replace the (single) offset 0148h with
some name, then data is the filename at the end "SNAP",0. Obviously
the B's prefixing the addresses mean "BYTE PTR", and ALL the numbers
are in HEX.
=Z10 0
=NSUC0.COM
=L
0000004D
=U100 147
1CB6:0100 B8 30 11 MOV AX,1130
1CB6:0103 32 FF XOR BH,BH
1CB6:0105 CD 10 INT 10 ;DL=rows-1
1CB6:0107 B4 0F MOV AH,0F
1CB6:0109 CD 10 INT 10 ;AH=columns
1CB6:010B 0E PUSH CS ;1st BIOS call
1CB6:010C 07 POP ES ;corrupts ES
1CB6:010D 52 PUSH DX ;
1CB6:010E 50 PUSH AX ;set B[BP+1]=columns
1CB6:010F 8B EC MOV BP,SP ; B[BP+2]=rows
1CB6:0111 BA 48 01 MOV DX,0148 ;open (CREATE) file
1CB6:0114 33 C9 XOR CX,CX ;name "SNAP"
1CB6:0116 B4 3C MOV AH,3C
1CB6:0118 CD 21 INT 21
1CB6:011A 93 XCHG BX,AX ;handle stays in BX
1CB6:011B 33 F6 XOR SI,SI ;SI read screen offset
1CB6:011D BA 80 00 MOV DX,0080 ;DX data store in PSP
1CB6:0120 B8 00 B8 MOV AX,B800
1CB6:0123 8E E0 MOV FS,AX ;FS screen segment
1CB6:0125 8B FA MOV DI,DX ;outer loop rows
1CB6:0127 0F B6 4E 01 MOVZX CX,B [BP+0001] ;miss out the attribute
1CB6:012B 64 AD FS: LODSW ;byte, copying to
1CB6:012D AA STOSB ;DS:080
1CB6:012E E2 FB LOOP 012B
1CB6:0130 B8 0D 0A MOV AX,0A0D ;n/l on row end
1CB6:0133 AB STOSW
1CB6:0134 8B CF MOV CX,DI
1CB6:0136 2B CA SUB CX,DX ;CX=data length
1CB6:0138 B4 40 MOV AH,40 ;write row to file
1CB6:013A CD 21 INT 21
1CB6:013C FE 4E 02 DEC B [BP+0002] ;loop for row count
1CB6:013F 79 E4 JNS 0125
1CB6:0141 66 58 POP EAX ;clean-up stack
1CB6:0143 B4 3E MOV AH,3E ;close file
1CB6:0145 CD 21 INT 21
1CB6:0147 C3 RET ;go CS:0 !
=D148 14C
1CB6:0148 53 4E 41 50 00 SNAP
=Q
If you've never seen the 2 BIOS calls before then you'd better take a look
at ralf brown's legendary interrupt list.
You may always overide the source segment DS: on a string instruction,
but you cannot override the destination segment ES: ever.
It's left as an exercise for you to incorporate error handling (since there
is none) and still better the length of this code ;)
::/ \::::::.
:/___\:::::::.
/| \::::::::.
:| _/\:::::::::.
:| _|\ \::::::::::.
:::\_____\:::::::::::.......................................................FIN