875 lines
16 KiB
HTML
875 lines
16 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|
<HTML
|
|
><HEAD
|
|
><TITLE
|
|
>Frequently Asked Questions</TITLE
|
|
><META
|
|
NAME="GENERATOR"
|
|
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
|
|
REL="HOME"
|
|
TITLE="Linux Assembly HOWTO"
|
|
HREF="index.html"><LINK
|
|
REL="PREVIOUS"
|
|
TITLE="Resources"
|
|
HREF="resources.html"><LINK
|
|
REL="NEXT"
|
|
TITLE="History"
|
|
HREF="history.html"></HEAD
|
|
><BODY
|
|
CLASS="chapter"
|
|
BGCOLOR="#FFFFFF"
|
|
TEXT="#000000"
|
|
LINK="#0000FF"
|
|
VLINK="#840084"
|
|
ALINK="#0000FF"
|
|
><DIV
|
|
CLASS="NAVHEADER"
|
|
><TABLE
|
|
SUMMARY="Header navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TH
|
|
COLSPAN="3"
|
|
ALIGN="center"
|
|
>Linux Assembly HOWTO</TH
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="left"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="resources.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="80%"
|
|
ALIGN="center"
|
|
VALIGN="bottom"
|
|
></TD
|
|
><TD
|
|
WIDTH="10%"
|
|
ALIGN="right"
|
|
VALIGN="bottom"
|
|
><A
|
|
HREF="history.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"></DIV
|
|
><DIV
|
|
CLASS="chapter"
|
|
><H1
|
|
><A
|
|
NAME="AEN938"
|
|
></A
|
|
>Chapter 8. Frequently Asked Questions</H1
|
|
><P
|
|
> Here are frequently asked questions (with answers)
|
|
about Linux assembly programming.
|
|
Some of the questions (and the answers) were taken from the
|
|
the linux-assembly mailing list.
|
|
</P
|
|
><DIV
|
|
CLASS="qandaset"
|
|
><DL
|
|
><DT
|
|
>8.1. <A
|
|
HREF="faq.html#AEN944"
|
|
> How do I do graphics programming in Linux?
|
|
</A
|
|
></DT
|
|
><DT
|
|
>8.2. <A
|
|
HREF="faq.html#AEN952"
|
|
> How do I debug pure assembly code under Linux?
|
|
</A
|
|
></DT
|
|
><DT
|
|
>8.3. <A
|
|
HREF="faq.html#AEN975"
|
|
>Any other useful debugging tools?</A
|
|
></DT
|
|
><DT
|
|
>8.4. <A
|
|
HREF="faq.html#AEN985"
|
|
> How do I access BIOS functions from Linux (BSD, BeOS, etc)?
|
|
</A
|
|
></DT
|
|
><DT
|
|
>8.5. <A
|
|
HREF="faq.html#AEN992"
|
|
> Is it possible to write kernel modules in assembly?
|
|
</A
|
|
></DT
|
|
><DT
|
|
>8.6. <A
|
|
HREF="faq.html#AEN1009"
|
|
> How do I allocate memory dynamically?
|
|
</A
|
|
></DT
|
|
><DT
|
|
>8.7. <A
|
|
HREF="faq.html#AEN1022"
|
|
> I can't understand how to use <TT
|
|
CLASS="function"
|
|
>select</TT
|
|
> system call!
|
|
</A
|
|
></DT
|
|
></DL
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN944"
|
|
></A
|
|
><B
|
|
>8.1. </B
|
|
>
|
|
How do I do graphics programming in Linux?
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
An answer from Paul Furber:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="screen"
|
|
> Ok you have a number of options to graphics in Linux. Which one you use
|
|
depends on what you want to do. There isn't one Web site with all the
|
|
information but here are some tips:
|
|
|
|
SVGALib: This is a C library for console SVGA access.
|
|
Pros: very easy to learn, good coding examples, not all that different
|
|
from equivalent gfx libraries for DOS, all the effects you know from DOS
|
|
can be converted with little difficulty.
|
|
Cons: programs need superuser rights to run since they write directly to
|
|
the hardware, doesn't work with all chipsets, can't run under X-Windows.
|
|
Search for svgalib-1.4.x on http://ftp.is.co.za
|
|
|
|
Framebuffer: do it yourself graphics at SVGA res
|
|
Pros: fast, linear mapped video access, ASM can be used if you want :)
|
|
Cons: has to be compiled into the kernel, chipset-specific issues, must
|
|
switch out of X to run, relies on good knowledge of linux system calls
|
|
and kernel, tough to debug
|
|
Examples: asmutils (http://www.linuxassembly.org) and the leaves example
|
|
and my own site for some framebuffer code and tips in asm
|
|
(http://ma.verick.co.za/linux4k/)
|
|
|
|
Xlib: the application and development libraries for XFree86.
|
|
Pros: Complete control over your X application
|
|
Cons: Difficult to learn, horrible to work with and requires quite a bit
|
|
of knowledge as to how X works at the low level.
|
|
Not recommended but if you're really masochistic go for it. All the
|
|
include and lib files are probably installed already so you have what
|
|
you need.
|
|
|
|
Low-level APIs: include PTC, SDL, GGI and Clanlib
|
|
Pros: very flexible, run under X or the console, generally abstract away
|
|
the video hardware a little so you can draw to a linear surface, lots of
|
|
good coding examples, can link to other APIs like OpenGL and sound libs,
|
|
Windows DirectX versions for free
|
|
Cons: Not as fast as doing it yourself, often in development so versions
|
|
can (and do) change frequently.
|
|
Examples: PTC and GGI have excellent demos, SDL is used in sdlQuake,
|
|
Myth II, Civ CTP and Clanlib has been used for games as well.
|
|
|
|
High-level APIs: OpenGL - any others?
|
|
Pros: clean api, tons of functionality and examples, industry standard
|
|
so you can learn from SGI demos for example
|
|
Cons: hardware acceleration is normally a must, some quirks between
|
|
versions and platforms
|
|
Examples: loads - check out www.mesa3d.org under the links section.
|
|
|
|
To get going try looking at the svgalib examples and also install SDL
|
|
and get it working. After that, the sky's the limit.
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN952"
|
|
></A
|
|
><B
|
|
>8.2. </B
|
|
>
|
|
How do I debug pure assembly code under Linux?
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
There's an early version of the
|
|
Assembly Language Debugger,
|
|
which is designed to work with assembly code,
|
|
and is portable enough to run on Linux and *BSD.
|
|
It is already functional and should be the right choice, check it out!
|
|
</P
|
|
><P
|
|
> You can also try <B
|
|
CLASS="command"
|
|
>gdb</B
|
|
> ;).
|
|
Although it is source-level debugger, it can be used to debug
|
|
pure assembly code, and with some trickery you can make
|
|
<B
|
|
CLASS="command"
|
|
>gdb</B
|
|
> to do what you need
|
|
(unfortunately, nasm '-g' switch does not generate
|
|
proper debug info for gdb; this is nasm bug, I think).
|
|
Here's an answer from Dmitry Bakhvalov:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="screen"
|
|
> Personally, I use gdb for debugging asmutils. Try this:
|
|
|
|
1) Use the following stuff to compile:
|
|
$ nasm -f elf -g smth.asm
|
|
$ ld -o smth smth.o
|
|
|
|
2) Fire up gdb:
|
|
$ gdb smth
|
|
|
|
3) In gdb:
|
|
(gdb) disassemble _start
|
|
Place a breakpoint at _start+1 (If placed at _start the breakpoint
|
|
wouldnt work, dunno why)
|
|
(gdb) b *0x8048075
|
|
|
|
To step thru the code I use the following macro:
|
|
(gdb)define n
|
|
>ni
|
|
>printf "eax=%x ebx=%x ...etc...",$eax,$ebx,...etc...
|
|
>disassemble $pc $pc+15
|
|
>end
|
|
|
|
Then start the program with r command and debug with n.
|
|
|
|
Hope this helps.
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
> An additional note from ???:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="screen"
|
|
> I have such a macro in my .gdbinit for quite some time now, and it
|
|
for sure makes life easier. A small difference : I use "x /8i $pc",
|
|
which guarantee a fixed number of disassembled instructions. Then,
|
|
with a well chosen size for my xterm, gdb output looks like it is
|
|
refreshed, and not scrolling.
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
> If you want to set breakpoints across your code, you can just use
|
|
<TT
|
|
CLASS="function"
|
|
>int 3</TT
|
|
> instruction as breakpoint
|
|
(instead of entering address manually in <B
|
|
CLASS="command"
|
|
>gdb</B
|
|
>).
|
|
</P
|
|
><P
|
|
> If you're using <SPAN
|
|
CLASS="application"
|
|
>gas</SPAN
|
|
>, you should consult
|
|
<SPAN
|
|
CLASS="application"
|
|
>gas</SPAN
|
|
> and <SPAN
|
|
CLASS="application"
|
|
>gdb</SPAN
|
|
> related
|
|
tutorials.
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN975"
|
|
></A
|
|
><B
|
|
>8.3. </B
|
|
>Any other useful debugging tools?</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
Definitely <B
|
|
CLASS="command"
|
|
>strace</B
|
|
> can help a lot
|
|
(<B
|
|
CLASS="command"
|
|
>ktrace</B
|
|
> and <B
|
|
CLASS="command"
|
|
>kdump</B
|
|
>
|
|
on FreeBSD),
|
|
it is used to trace system calls and signals.
|
|
Read its manual page (<B
|
|
CLASS="command"
|
|
>man strace</B
|
|
>) and
|
|
<B
|
|
CLASS="command"
|
|
>strace - -help</B
|
|
> output for details.
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN985"
|
|
></A
|
|
><B
|
|
>8.4. </B
|
|
>
|
|
How do I access BIOS functions from Linux (BSD, BeOS, etc)?
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
Short answer is -- noway. This is protected mode, use OS services instead.
|
|
Again, you can't use <TT
|
|
CLASS="function"
|
|
>int 0x10</TT
|
|
>,
|
|
<TT
|
|
CLASS="function"
|
|
>int 0x13</TT
|
|
>, etc.
|
|
Fortunately almost everything can be implemented
|
|
by means of system calls or library functions.
|
|
In the worst case you may go through direct port access,
|
|
or make a kernel patch to implement needed functionality,
|
|
or use LRMI library to access BIOS functions.
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN992"
|
|
></A
|
|
><B
|
|
>8.5. </B
|
|
>
|
|
Is it possible to write kernel modules in assembly?
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
Yes, indeed it is. While in general it is not a good idea
|
|
(it hardly will speedup anything), there may be a need of such wizardy.
|
|
The process of writing a module itself is not that hard - -
|
|
a module must have some predefined global function,
|
|
it may also need to call some external functions from the kernel.
|
|
Examine kernel source code (that can be built as module) for details.
|
|
</P
|
|
><P
|
|
> Meanwhile, here's an example of a minimum dumb kernel module
|
|
(<TT
|
|
CLASS="filename"
|
|
>module.asm</TT
|
|
>)
|
|
(source is based on example by mammon_ from APJ #8):
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="programlisting"
|
|
> section .text
|
|
|
|
global init_module
|
|
global cleanup_module
|
|
global kernel_version
|
|
|
|
extern printk
|
|
|
|
init_module:
|
|
push dword str1
|
|
call printk
|
|
pop eax
|
|
xor eax,eax
|
|
ret
|
|
|
|
cleanup_module:
|
|
push dword str2
|
|
call printk
|
|
pop eax
|
|
ret
|
|
|
|
str1 db "init_module done",0xa,0
|
|
str2 db "cleanup_module done",0xa,0
|
|
|
|
kernel_version db "2.2.18",0
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
> The only thing this example does is reporting its actions.
|
|
Modify <TT
|
|
CLASS="filename"
|
|
>kernel_version</TT
|
|
> to match yours, and build module with:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="screen"
|
|
> $ nasm -f elf -o module.m module.asm
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="screen"
|
|
> $ ld -r -o module.o module.m
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
><P
|
|
> Now you can play with it using <B
|
|
CLASS="command"
|
|
>insmod/rmmod/lsmod</B
|
|
>
|
|
(root privilidged are required); a lot of fun, huh?
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN1009"
|
|
></A
|
|
><B
|
|
>8.6. </B
|
|
>
|
|
How do I allocate memory dynamically?
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
A laconic answer from H-Peter Recktenwald:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="programlisting"
|
|
> ebx := 0 (in fact, any value below .bss seems to do)
|
|
sys_brk
|
|
eax := current top (of .bss section)
|
|
|
|
ebx := [ current top < ebx < (esp - 16K) ]
|
|
sys_brk
|
|
eax := new top of .bss
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
An extensive answer from Tiago Gasiba:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="programlisting"
|
|
> section .bss
|
|
|
|
var1 resb 1
|
|
|
|
section .text
|
|
|
|
;
|
|
;allocate memory
|
|
;
|
|
|
|
%define LIMIT 0x4000000 ; about 100Megs
|
|
|
|
mov ebx,0 ; get bottom of data segment
|
|
call sys_brk
|
|
|
|
cmp eax,-1 ; ok?
|
|
je erro1
|
|
|
|
add eax,LIMIT ; allocate +LIMIT memory
|
|
mov ebx,eax
|
|
call sys_brk
|
|
|
|
cmp eax,-1 ; ok?
|
|
je erro1
|
|
|
|
cmp eax,var1+1 ; has the data segment grown?
|
|
je erro1
|
|
|
|
;
|
|
;use allocated memory
|
|
;
|
|
; now eax contains bottom of
|
|
; data segment
|
|
mov ebx,eax ; save bottom
|
|
mov eax,var1 ; eax=beginning of data segment
|
|
repeat:
|
|
mov word [eax],1 ; fill up with 1's
|
|
inc eax
|
|
cmp ebx,eax ; current pos = bottom?
|
|
jne repeat
|
|
|
|
;
|
|
;free memory
|
|
;
|
|
|
|
mov ebx,var1 ; deallocate memory
|
|
call sys_brk ; by forcing its beginning=var1
|
|
|
|
cmp eax,-1 ; ok?
|
|
je erro2
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
><DIV
|
|
CLASS="qandaentry"
|
|
><DIV
|
|
CLASS="question"
|
|
><P
|
|
><A
|
|
NAME="AEN1022"
|
|
></A
|
|
><B
|
|
>8.7. </B
|
|
>
|
|
I can't understand how to use <TT
|
|
CLASS="function"
|
|
>select</TT
|
|
> system call!
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="answer"
|
|
><P
|
|
><B
|
|
> </B
|
|
>
|
|
An answer from Patrick Mochel:
|
|
</P
|
|
><P
|
|
> <TABLE
|
|
BORDER="0"
|
|
BGCOLOR="#E0E0E0"
|
|
WIDTH="100%"
|
|
><TR
|
|
><TD
|
|
><FONT
|
|
COLOR="#000000"
|
|
><PRE
|
|
CLASS="programlisting"
|
|
> When you call sys_open, you get back a file descriptor, which is simply an
|
|
index into a table of all the open file descriptors that your process has.
|
|
stdin, stdout, and stderr are always 0, 1, and 2, respectively, because
|
|
that is the order in which they are always open for your process from there.
|
|
Also, notice that the first file descriptor that you open yourself (w/o first
|
|
closing any of those magic three descriptors) is always 3, and they increment
|
|
from there.
|
|
|
|
Understanding the index scheme will explain what select does. When you
|
|
call select, you are saying that you are waiting certain file descriptors
|
|
to read from, certain ones to write from, and certain ones to watch from
|
|
exceptions from. Your process can have up to 1024 file descriptors open,
|
|
so an fd_set is just a bit mask describing which file descriptors are valid
|
|
for each operation. Make sense?
|
|
|
|
Since each fd that you have open is just an index, and it only needs to be
|
|
on or off for each fd_set, you need only 1024 bits for an fd_set structure.
|
|
1024 / 32 = 32 longs needed to represent the structure.
|
|
|
|
Now, for the loose example.
|
|
Suppose you want to read from a file descriptor (w/o timeout).
|
|
|
|
- Allocate the equivalent to an fd_set.
|
|
|
|
.data
|
|
|
|
my_fds: times 32 dd 0
|
|
|
|
- open the file descriptor that you want to read from.
|
|
|
|
- set that bit in the fd_set structure.
|
|
|
|
First, you need to figure out which of the 32 dwords the bit is in.
|
|
|
|
Then, use bts to set the bit in that dword. bts will do a modulo 32
|
|
when setting the bit. That's why you need to first figure out which
|
|
dword to start with.
|
|
|
|
mov edx, 0
|
|
mov ebx, 32
|
|
div ebx
|
|
|
|
lea ebx, my_fds
|
|
bts ebx[eax * 4], edx
|
|
|
|
- repeat the last step for any file descriptors you want to read from.
|
|
|
|
- repeat the entire exercise for either of the other two fd_sets if you want action from them.
|
|
|
|
That leaves two other parts of the equation - the n paramter and the timeout
|
|
parameter. I'll leave the timeout parameter as an exercise for the reader
|
|
(yes, I'm lazy), but I'll briefly talk about the n parameter.
|
|
|
|
It is the value of the largest file descriptor you are selecting from (from
|
|
any of the fd_sets), plus one. Why plus one? Well, because it's easy to
|
|
determine a mask from that value. Suppose that there is data available on
|
|
x file descriptors, but the highest one you care about is (n - 1). Since
|
|
an fd_set is just a bitmask, the kernel needs some efficient way for
|
|
determining whether to return or not from select. So, it masks off the bits
|
|
that you care about, checks if anything is available from the bits that are
|
|
still set, and returns if there is (pause as I rummage through kernel source).
|
|
Well, it's not as easy as I fantasized it would be. To see how the kernel
|
|
determines that mask, look in fs/select.c in the kernel source tree.
|
|
|
|
Anyway, you need to know that number, and the easiest way to do it is to save
|
|
the value of the last file descriptor open somewhere so you don't lose it.
|
|
|
|
Ok, that's what I know. A warning about the code above (as always) is that
|
|
it is not tested. I think it should work, but if it doesn't let me know.
|
|
But, if it starts a global nuclear meltdown, don't call me. ;-)
|
|
</PRE
|
|
></FONT
|
|
></TD
|
|
></TR
|
|
></TABLE
|
|
>
|
|
</P
|
|
></DIV
|
|
></DIV
|
|
></DIV
|
|
><P
|
|
> <EM
|
|
>That's all for now, folks</EM
|
|
>.
|
|
</P
|
|
></DIV
|
|
><DIV
|
|
CLASS="NAVFOOTER"
|
|
><HR
|
|
ALIGN="LEFT"
|
|
WIDTH="100%"><TABLE
|
|
SUMMARY="Footer navigation table"
|
|
WIDTH="100%"
|
|
BORDER="0"
|
|
CELLPADDING="0"
|
|
CELLSPACING="0"
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="resources.html"
|
|
ACCESSKEY="P"
|
|
>Prev</A
|
|
></TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="index.html"
|
|
ACCESSKEY="H"
|
|
>Home</A
|
|
></TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
><A
|
|
HREF="history.html"
|
|
ACCESSKEY="N"
|
|
>Next</A
|
|
></TD
|
|
></TR
|
|
><TR
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="left"
|
|
VALIGN="top"
|
|
>Resources</TD
|
|
><TD
|
|
WIDTH="34%"
|
|
ALIGN="center"
|
|
VALIGN="top"
|
|
> </TD
|
|
><TD
|
|
WIDTH="33%"
|
|
ALIGN="right"
|
|
VALIGN="top"
|
|
>History</TD
|
|
></TR
|
|
></TABLE
|
|
></DIV
|
|
></BODY
|
|
></HTML
|
|
> |