[net.micro.amiga] Remarks on AmigaDos

hamilton@uiucuxc.CSO.UIUC.EDU (10/26/86)

claudio@ethz says:
> We tried to write a program dosspy which should monitor some
> AmigaDos calls.
>	[discussion of dos.library's nonstandard vector]
> After mastering this problem we tried our dosspy with some programs
> and several dos commands. It worked with programs, but displayed
> nothing with dos commands. It seems that commands (as rename)
> doesn't call dos.library through the library vectors. To find out more
> we disassembled rename. Here the first 3 instructions of rename:
> 
> 			MOVEA.L	$164(A2),A4
> 			MOVEQ.L $C,D0
> 			JSR	(A5)
> 
> Seems that a program starts not only with A0 pointing to the arguments
> and D0 indicating the number of the characters in the argument. At least
> A2 and A5 are initialised with some magic values pointing probably to
> the function dispatcher (A5) and to some global values (A2) of AmigaDos.

    i've been planning to write up what i've learned from disassembling
AmigaDos.  this is a good time to mention a few things.

    this is the linkage convention used by BCPL programs.  A2 contains the
"Global Base"; in some ways, it is analogous to A4 in manx's "small model",
and it points to BCPL's equivalent of C's global data.  in AmigaDos, the
major purpose of this global area is to provide pointers-to-functions --
a global function will have it's address in the GV.  processes started by
AmigaDos get a copy of dos's GV.  if you are a BCPL program, or an asm/C
program masquerading as BCPL, you can call dos directly via the GV (but
you'd have to follow BCPL calling conventions -- more trouble than it's
worth).

    by the way, this is a large part of the reason why "processes" can
make AmigaDos calls but "tasks" can't.  tasks are created by Exec in the
asm/C mileau, and they are not setup to follow the BCPL linkage regime.
however, i think an Exec task _can_ do file system i/o if it deals
directly with a file handler via DosPackets.

    dos includes the BCPL flavor of the C stdio library (including printf)
and functions like the command-template-parser.  this is why BCPL commands
are relatively compact, in spite of their poor code.  most of their "library"
code is already accessible in WCS!

    A1 points to BCPL's "local base".  this works like the stack in C, and
in fact occupies the same memory block.  it grows up from the bottom, while
the "real" stack grows down from the top.  SP is used almost exclusively as
a means of reading PC, via the jsr instruction.  as you'll see below, BCPL
return addresses don't stay at SP for long.  when AmigaDos calls Exec or
Intuition, SP resumes its familiar functions.

    A4 is the "program base"; it points at the beginning of the currently
active function.  A5 and A6 point at the BCPL "call" and "return" wedges
(if you've ever looked at "csv" and "cret" in unix, these will ring bells).

    D1 thru D4 contain the first 4 function arguments; these are "echoed"
on the local base, where additional arguments (if any) appear.  D1 is also
used to return function values, and any of these registers, plus D5-D7,
can be used to store intermediate results of calculations.  D0 is used by
the linkage routines to convey the size of the current function's local base.
BCPL plays some interesting games with this.  by under-counting, you cause
a called function to "share" your "local" variables(!).  this value can
vary throughout a single function, just as C's stack pointer will fluctuate
when you enter and leave compound statement blocks with auto variable
declarations.

    A3 is used as a temp register, and A0 is usually zero so that data
registers can double as address registers (eg, move.l D1,XXX(A0,Dn)).

    here's a simple example of function-call linkage.  paraphrasing in C:

	a()
	{
		long x;

		x = b(1L, 2L, 3L, 4L);
	}

	b(q, r, s, t)
		long q, r, s, t;
	{
		long i;
		...
		return(i);
	}

before a() calls b(), the stack looks roughly like this:

	TOS->					<- task.tc_SPUpper
		.	[highest word on stack]
		.	[Exec task args up here]
		.	[return address in exec or dos]
	SP->	.	[high end of stack used by jsr's]
		.
		.
		.
	A1->	x	a's local base - local variables
		pb	a's saved program base
		ret	return address in caller-of-a
		old A1	saved local base of caller-of-a
		.
		.
		.	[lowest word on stack]	<- task.tc_SPLower

a() has no args, so its local variables are at the start of its local
base.  when a() calls b(), the following takes place:

	--- in a() ---
	 1 load the first 4 parameters into D1-D4.
	 2 calculate the local base "stride".  in simple cases, the
	   stride is (12 + size of local variables).  the 12 is for
	   the 3 longwords of saved function context.  since a() has
	   0 args and 1 longword of local variables, its stride is
	   (12 + 4) = 16.
	 3 if there are more than 4 parameters, store them at offsets
	   starting at 16 + stride from the current A1.  the 16 is for
	   the shadow copies of D1-D4 that will be made.
	 4 load the local base stride into D0.
	 5 load the address of the function to be called (b()) into A4.
	   for global functions, this is done by a "move.l ###(A2),A4";
	   for non-global functions, by a "lea ###(A4),A4".
	 6 push the address of the next instruction in a() onto the stack
	   and enter the BCPL "call" mechanism (via "jsr (A5)").
	--- in the BCPL call mechanism ---
	 7 pop a's return address into A3.
	 8 save a's local base (A1) at offset -12 from what will become
	   b's local base (A1 + "stride" from above).
	 9 save a's return address (A3) at offset -8.
	10 save b's program base (A4) at offset -4.
	11 advance A1 to become b's local base (via "add.l D0,A1").
	12 store D1-D4 (b's args) on the new local base.
	11 copy A4 to PC (via "jmp (A4)") -- enter b().

the stack will now look like:

	TOS->					<- task.tc_SPUpper
		.
	SP->	.
		.
		i	b'a local variables
		.	[extra args would have gone here]
		t
		s
		r	shadow copies of D1-D4
	A1->	q	b's local base, args first
		pb	b's program base
		ret	return address in a
		old A1	a's local base pointer
		x	a's local variables
		pb	a's program base
		ret	return address to caller of a
		old A1	saved local base of caller of a
		.
		.
		.	[lowest word on stack]	<- task.tc_SPLower

b() can access its args via D1-D4 (until those registers get recycled
for imtermediate results) or via "offset(A1)".  local variables are
accessed via "offset(A1)".  literals are accessed via "offset(A4)" (space
is allocated for them at the end of each function's code).  globals are
accessed via "offset(A2)", and non-global externals (sometimes called
"file static" in C) are accessed like literals via "offset(A4)".

when b() returns to a():

	--- in b() ---
	1 load return value (if any) into D1.
	2 transfer to BCPL "return" mechanism (via "jmp (A6)").
	--- in BCPL return mechanism ---
	3 load A3 from (A1)[-8]: return address in a().
	4 load A1 from (A1)[-12]: restore a's local base.
	5 load A4 from (A1)[-4]: restore a's program base.
	  note that this is saved relative to _a_'s local base;
	  we re-loaded A1.
	6 copy A3 to PC (via "jmp (A3)"): return to a().
	--- in a() ---
	7 access return value in D1.

    this is a rather hasty dissertation.  if people are really eager
for more info about this, let me know.

> So if anybody ever had the idea to exchange the AmigaDos function by
> own functions (e.g. try to implement a second filesystem which would
> coexist with the present one and be transparent to the user) he will
> have to rewrite all dos functions !

    no, that's not necessary.  but, it probably IS desirable.  if AmigaDos
were re-written in asm/C, i suspect there'd be a LOT of extra room in WCS
for some extended functionality.  it may be possible to re-do AmigaDos
completely, while providing AmigaDos compatibility.  if you're willing to
toss the BCPL programs and replace them with asm/C equivalents, along with
a new shell, it gets even better.  the only thing that really bothers me
is that some developers have already resorted to peek's and poke's out of
desperation (eg: manx's fexec), and others are _bound_ to exploit some
foibles of AmigaDos (such as the exclusiveness of write-open's).  you'd
have to decide whether to preserve AmigaDos's file path vocabulary (which
people are probably just getting used to) or to adopt a more common
standard (MSDOS or Unix), or to try to do both.  and of course, you'd
have to preserve those #$%^&*! BPTRs for posterity.
    to implement a new file system, it suffices to write a file-handler.
"l:ram-handler" is an example.  the handler code demos that have been posted
recently point the way.  once you get yourself "mounted" as an AmigaDos
device process, you just sit back and wait for those packets.

	wayne hamilton
	U of Il and US Army Corps of Engineers CERL
UUCP:	{ihnp4,pur-ee,convex}!uiucdcs!uiucuxc!hamilton
ARPA:	hamilton%uiucuxc@a.cs.uiuc.edu	USMail:	Box 476, Urbana, IL 61801
CSNET:	hamilton%uiucuxc@uiuc.csnet	Phone:	(217)333-8703
CIS:    [73047,544]			PLink: w hamilton