By Leonardo Giordani Published on

The complete Exec vector table

In the 3rd post of this series I showed the Exec vectors table and the way MakeFunctions creates the jump table when Exec is installed in memory. In that post I focused on the first 4 reserved vectors, but it is useful to have the full table while reading the Kickstart code. This is the full vectors table for Exec 34.2 (28 Oct 1987).

The Relative offset column contains the offset of the function in the jump table once the library has been installed, which allows any Amiga program to call Exec functions through the offset(a6) syntax (e.g. OpenLibrary can be called by jsr -0x228(a6)). Vector position is the offset of the vector in the Kickstart 1.3 ROM (i.e. 0x0001a7c is the position of the vector table itself), Relative address is the hexadecimal value stored in the table before the relocation, Absolute address is the function position after relocation (i.e. the wrapped sum between the table address and the relative address), and Function is the function name according to the include files and the Amiga documentation.

Relative offset Vector position Relative address Absolute address Function
-0x00 00001a7c 08a0 0000231c Open()
-0x06 00001a7e 08a8 00002324 Close()
-0x12 00001a80 08ac 00002328 Expunge()
-0x18 00001a82 08ac 00002328 Reserved for future use
-0x1e 00001a84 ee6a 000008e6 Supervisor()
-0x24 00001a86 f420 00000e9c ExitIntr()
-0x2a 00001a88 f446 00000ec2 Schedule()
-0x30 00001a8a 04f8 00001f74 Reschedule()
-0x36 00001a8c f4a0 00000f1c Switch()
-0x3c 00001a8e f4ea 00000f66 Dispatch()
-0x42 00001a90 f58e 0000100a Exception()
-0x48 00001a92 f0b0 00000b2c InitCode()
-0x4e 00001a94 f188 00000c04 InitStruct()
-0x54 00001a96 faac 00001528 MakeLibrary()
-0x5a 00001a98 fb36 000015b2 MakeFunctions()
-0x60 00001a9a f080 00000afc FindResident()
-0x66 00001a9c f0e8 00000b64 InitResident()
-0x6c 00001a9e 1596 00003012 Alert()
-0x72 00001aa0 08ee 0000236a Debug()
-0x78 00001aa2 f9ac 00001428 Disable()
-0x7e 00001aa4 f9ba 00001436 Enable()
-0x84 00001aa6 051a 00001f96 Forbid()
-0x8a 00001aa8 0520 00001f9c Permit()
-0x90 00001aaa f6e2 0000115e SetSR()
-0x96 00001aac f708 00001184 SuperState()
-0x9c 00001aae f734 000011b0 UserState()
-0xa2 00001ab0 f74e 000011ca SetIntVector()
-0xa8 00001ab2 f794 00001210 AddIntServer()
-0xae 00001ab4 f7d4 00001250 RemIntServer()
-0xb4 00001ab6 f8e0 0000135c Cause()
-0xba 00001ab8 fc5c 000016d8 Allocate()
-0xc0 00001ac0 fcc4 00001740 Deallocate()
-0xc6 00001abc fd54 000017d0 AllocMem()
-0xcc 00001abe fe00 0000187c AllocAbs()
-0xd2 00001ac0 fdb0 0000182c FreeMem()
-0xd8 00001ac2 fe90 0000190c AvailMem()
-0xde 00001ac4 fede 0000195a AllocEntry()
-0xe4 00001ac6 ff6c 000019e8 FreeEntry()
-0xea 00001ac8 fb6c 000015e8 Insert()
-0xf0 00001aca fb98 00001614 AddHead()
-0xf6 00001acc fba8 00001624 AddTail()
-0xfc 00001ace fbc0 0000163c Remove()
-0x102 00001ad0 fbce 0000164a RemHead()
-0x108 00001ad2 fbde 0000165a RemTail()
-0x10e 00001ad4 fbf4 00001670 Enqueue()
-0x114 00001ad6 fc1a 00001696 FindName()
-0x11a 00001ad8 0208 00001c84 AddTask()
-0x120 00001ada 02b4 00001d30 RemTask()
-0x126 00001adc 0334 00001db0 FindTask()
-0x12c 00001ade 0388 00001e04 SetTaskPri()
-0x132 00001ae0 03e2 00001e5e SetSignal()
-0x138 00001ae2 03d8 00001e54 SetExcept()
-0x13e 00001ae4 0490 00001f0c Wait()
-0x144 00001ae6 0408 00001e84 Signal()
-0x14a 00001ae8 0584 00002000 AllocSignal()
-0x150 00001aea 05bc 00002038 FreeSignal()
-0x156 00001aec 054e 00001fca AllocTrap()
-0x15c 00001aee 0574 00001ff0 FreeTrap()
-0x162 00001af0 00d8 00001b54 AddPort()
-0x168 00001af2 00f0 00001b6c RemPort()
-0x16e 00001af4 00f4 00001b70 PutMsg()
-0x174 00001af6 016e 00001bea GetMsg()
-0x17a 00001af8 019c 00001c18 ReplyMsg()
-0x180 00001afa 01b6 00001c32 WaitPort()
-0x186 00001afc 01de 00001c5a FindPort()
-0x18c 00001afe f9cc 00001448 AddLibrary()
-0x192 00001b00 f9da 00001456 RemLibrary()
-0x198 00001b02 f9f0 0000146c OldOpenLibrary()
-0x19e 00001b04 fa26 000014a2 CloseLibrary()
-0x1a4 00001b06 fa3a 000014b6 SetFunction()
-0x1aa 00001b08 fa58 000014d4 SumLibrary()
-0x1b0 00001b0a ec14 00000690 AddDevice()
-0x1b6 00001b0c ec22 0000069e RemDevice()
-0x1bc 00001b0e ec26 000006a2 OpenDevice()
-0x1c2 00001b10 ec74 000006f0 CloseDevice()
-0x1c8 00001b12 ec9c 00000718 DoIO()
-0x1ce 00001b14 ec8a 00000706 SendIO()
-0x1d4 00001b16 ed0e 0000078a CheckIO()
-0x1da 00001b18 ecb2 0000072e WaitIO()
-0x1e0 00001b1a ed2a 000007a6 AbortIO()
-0x1e6 00001b1c 01e8 00001c64 AddResource()
-0x1ec 00001b1e 01f0 00001c6c RemResource()
-0x1f2 00001b20 01f4 00001c70 OpenResource()
-0x1f8 00001b22 07b8 00002234 execPrivate7()
-0x1fe 00001b24 07c2 0000223e execPrivate8()
-0x204 00001b26 07ee 0000226a execPrivate9()
-0x20a 00001b28 06a8 00002124 RawDoFmt()
-0x210 00001b2a f700 0000117c GetCC()
-0x216 00001b2c fdda 00001856 TypeOfMem()
-0x21c 00001b2e 131c 00002d98 Procure()
-0x222 00001b30 1332 00002dae Vacate()
-0x228 00001b32 f9f8 00001474 OpenLibrary()
-0x22e 00001b34 1354 00002dd0 InitSemaphore()
-0x234 00001b36 1374 00002df0 ObtainSemaphore()
-0x23a 00001b38 13c4 00002e40 ReleaseSemaphore()
-0x240 00001b3a 1428 00002ea4 AttemptSemaphore()
-0x246 00001b3c 1458 00002ed4 ObtainSemaphoreList()
-0x24c 00001b3e 14ce 00002f4a ReleaseSemaphoreList()
-0x252 00001b40 14f4 00002f70 FindSemaphore()
-0x258 00001b42 14e4 00002f60 AddSemaphore()
-0x25e 00001b44 14f0 00002f6c RemSemaphore()
-0x264 00001b46 effc 00000a78 SumKickData()
-0x26a 00001b48 ffaa 00001a26 AddMemList()
-0x270 00001b4a 1504 00002f80 CopyMem()
-0x276 00001b4c 1500 00002f7c CopyMemQuick()

The memory list header

Before we dig into the code of the AddMemList function to see how the memory space is added to the free memory list, let's have a look at the status of the memory list itself, as we need to be familiar with its structure to understand the rest of the process.

You might recall from the fifth article that the MemList structure is created 0x142 bytes after ExecBase, and that the structure is the following

    0xe +-------+ 0x150
        |       |
    0xd +-------+ 0x14f (LH_pad)
        | 0xa   |
    0xc +-------+ 0x14e (LH_TYPE)
        | 0x142 |
    0x8 +-------+ 0x14a (LH_TAILPRED)
        | 0x0   |
    0x4 +-------+ 0x146 (LH_TAIL)
        | 0x146 |
    0x0 +-------+ 0x142 (LH_HEAD)

This means that we can access the memory list with lea 0x142(a6),a0, as 0x142 is the relative address, i.e. assuming ExecBase is 0. Once the absolute address is in one of the address registers we can access the first node through (a0), which is the Motorola Assembly version of the C pointer dereference operation (Address Register Indirect Mode). With (a0) we do not use the content of a0, but the content of the memory location which address is contained in a0.

So if a0 is 0x142 in the previous figure, (a0) is 0x0 (0x142 contains 0x146, 0x146 contains 0x0). It's thus convenient to think of a0 as the pointer, and of (a0) as the value.

It is also interesting to note that the structure at 0x146 is very similar to the LN structure. Recall that the latter is defined in include_i/exec/nodes.i as

 STRUCTURE    LN,0    ; List Node
    APTR    LN_SUCC ; Pointer to next (successor)
    APTR    LN_PRED ; Pointer to previous (predecessor)
    UBYTE   LN_TYPE
    BYTE    LN_PRI  ; Priority, for sorting
    APTR    LN_NAME ; ID string, null terminated
    LABEL   LN_SIZE ; Note: word aligned

and as you can see LH_TAIL, LH_TAILPRED, and LH_TYPE can act as a proper node of a linked list. This is done on purpose (obviously), as the tail of the list has to be processed by the code that manages linked lists.

Adding free memory to the system list

At the end of the previous post we left Exec just after its code prepared the parameters of the free memory that was available on the system. As we saw in that post, this operation is always executed for the chip memory, and optionally for the fast memory if the hardware expansion is installed.

The AddMemList function at 0x1a26 is called with the following prototype

size = AddMemList(size, attributes, pri, base, name)
D0                D0    D1          D2   A0    A1

with the purpose of adding the given memory size, starting from the base address, to the system pool. The memory attributes will be store in the memory block and the priority (pri) will be used to find the insertion point. The name is also added to the memory node to identify it.

We can split the memory addition in two different parts. AddMemList will create a node that contain the parameters of the memory region, then will call Enqueue to add it to the MemList linked list. When Exec bootstraps the system the memory list is empty, but these functions must work in a generic case. Actually when the system has a memory expansion (fast memory), this is the first that is added to the list, but it's immediately followed by the chip memory.

To help you to follow what happens in the function, this is a depiction of the memory area that we want to add to the system pool at the end of AddMemList, just before the call to Enqueue. The area contains a header made of three structures, LN (linked list node), MH (memory header), and MC (memory chunk)

   SIZE +---------------+
        | Free memory   |
        +---------------+
        | ...           |
        +---------------+
        | Free memory   |
   0x2c +---------------+ <-+
        | MC_SIZE       |   |
   0x28 +---------------+   |
        | MC_BYTES      |   | MC
   0x24 +---------------+   |
        | MC_NEXT       |   |
   0x20 +---------------+ <-+
        | MH_FREE       |   |
   0x1c +---------------+   |
        | MH_UPPER      |   |
   0x18 +---------------+   |
        | MH_LOWER      |   | MH
   0x14 +---------------+   |
        | MH_FIRST      |   |
   0x10 +---------------+   |
        | MH_ATTRIBUTES |   |
    0xe +---------------+ <-+
        | LN_NAME       |   |
    0xa +---------------+   |
        | LN_PRI        |   |
    0x9 +---------------+   |
        | LN_TYPE       |   | LN
    0x8 +---------------+   |
        | LN_PRED       |   |
    0x4 +---------------+   |
        | LN_SUCC       |   |
    0x0 +---------------+ <-+

It is interesting to note that, since we are managing the system memory, we cannot store information about it other than in the memory itself. The presence of management structures, then, is reducing the amount of free memory. This has to taken into account when designing a memory management system, but will not be considered in this article.

When we create the structure shown in figure, we need to ensure that the actual free memory is a multiple of a long word (8 bytes), as this is generally desirable. The memory space we are dealing with starts from 0 in the picture, but can actually be anywhere in the system memory, so it is not guaranteed that either the beginning or the end of the free memory space are aligned with long words.

The code of AddMemList performs the alignment in two steps. First it aligns the address of MC to the upper nearest long word (multiple of 8), then aligns the total size to the lower nearest long word. For example, if the starting point was 0 and the total size 64 bytes, we would leave all the values untouched:LN_SUCC at 0x0, MC_NEXT at 0x20, and the total size of the chunk (structure 'MC' + free memory) would be 32 bytes.

   0x40 +---------------+ <-+
        | Free memory   |   |
        +---------------+   | 32 bytes
        | MC            |   |
   0x20 +---------------+ <-+
        | MH            |   |
        +---------------+   | 32 bytes
        | LN            |   |
    0x0 +---------------+ <-+

If the starting point was 1 and the total size 65 bytes, however, the result would be: LN_SUCC at 0x1, MC_NEXT at 0x28 (upper long word), and the total size of the memory would be 56 bytes (65-7 gives 58, rounded down to a multiple of 8), which means a chunk of 24 bytes

   0x42 +---------------+ <-+
        | Ignored       |   | 2 bytes
   0x40 +---------------+ <-+
        | Free memory   |   |
        +---------------+   | 24 bytes
        | MC            |   |
   0x28 +---------------+ <-+
        | EMPTY         |   | 7 bytes
   0x21 +---------------+ <-+
        | MH            |   |
        +---------------+   | 32 bytes
        | LN            |   |
    0x1 +---------------+ <-+

AddMemList internals

According to the Exec vectors table, AddMemList can be found at 00001a26, and ends at 00001a78. It is immediately followed by a padding word 0000 and the vectors table itself

00001a26: 2149 000a                 move.l  a1,0xa(a0)
00001a2a: 43e8 0020                 lea     0x20(a0),a1
00001a2e: 117c 000a 0008            move.b  #0xa,0x8(a0)
00001a34: 1142 0009                 move.b  d2,0x9(a0)
00001a38: 3141 000e                 move.w  d1,0xe(a0)
00001a3c: 2209                      move.l  a1,d1
00001a3e: 5e81                      addq.l  #0x7,d1
00001a40: 0201 00f8                 andi.b  #-0x8,d1
00001a44: c389                      exg     d1,a1
00001a46: 9289                      sub.l   a1,d1
00001a48: d081                      add.l   d1,d0
00001a4a: 0200 00f8                 andi.b  #-0x8,d0
00001a4e: 0480 0000 0020            subi.l  #0x20,d0
00001a54: 2149 0010                 move.l  a1,0x10(a0)
00001a58: 2149 0014                 move.l  a1,0x14(a0)
00001a5c: 2209                      move.l  a1,d1
00001a5e: d280                      add.l   d0,d1
00001a60: 2141 0018                 move.l  d1,0x18(a0)
00001a64: 2140 001c                 move.l  d0,0x1c(a0)
00001a68: 2340 0004                 move.l  d0,0x4(a1)
00001a6c: 4291                      clr.l   (a1)
00001a6e: 2248                      movea.l a0,a1
00001a70: 41ee 0142                 lea     0x142(a6),a0
00001a74: 6100 fc48                 bsr.w   0x16be
00001a78: 4e75                      rts     

Let's comment the function line by line. I will refer to the fields shown in the picture above by name, mentioning the offsets for clarity.

00001a26: 2149 000a                 move.l  a1,0xa(a0)

This code stores the address of the memory area name in LN_NAME (0xa(a0)).

00001a2a: 43e8 0020                 lea     0x20(a0),a1

This loads the absolute address of the memory chunk (structure MC). The purpose of this is to align the memory chunk to a long word later in the code.

00001a2e: 117c 000a 0008            move.b  #0xa,0x8(a0)
00001a34: 1142 0009                 move.b  d2,0x9(a0)
00001a38: 3141 000e                 move.w  d1,0xe(a0)

This stores 0xa in the LN_TYPE field (0x8(a0)). This corresponds to NT_MEMORY (see include_i/exec/nodes.i). It then copies the list priority into the LN_PRI field (0x9(a0)), and the memory attributes in the MH_ATTRIBUTES field (0xe(a0)).

00001a3c: 2209                      move.l  a1,d1
00001a3e: 5e81                      addq.l  #0x7,d1
00001a40: 0201 00f8                 andi.b  #-0x8,d1
00001a4e: 0480 0000 0020            subi.l  #0x20,d0

These three lines of code align the address of MC (a1) to a long word, adding 7 and removing the least significant 3 bits. Then the size of the headers (0x20) is removed from the total size of the memory region that was passed as an argument.

00001a54: 2149 0010                 move.l  a1,0x10(a0)
00001a58: 2149 0014                 move.l  a1,0x14(a0)

Store the first free memory location (a1) in MH_FIRST (0x10(a0)) and in MH_LOWER (0x14(a0)).

00001a5c: 2209                      move.l  a1,d1
00001a5e: d280                      add.l   d0,d1
00001a60: 2141 0018                 move.l  d1,0x18(a0)
00001a64: 2140 001c                 move.l  d0,0x1c(a0)

The size of the free memory is then added to the first free address and stored in MH_UPPER (0x18(a0)). The size of the free memory is stored in MH_FREE (0x1c(a0)).

00001a68: 2340 0004                 move.l  d0,0x4(a1)
00001a6c: 4291                      clr.l   (a1)

This code creates the memory chunk in the free memory area. The size of the chunk is stored in the MC_BYTES field (0x4(a1)), and the MC_NEXT field is cleared.

00001a6e: 2248                      movea.l a0,a1
00001a70: 41ee 0142                 lea     0x142(a6),a0
00001a74: 6100 fc48                 bsr.w   0x16be
00001a78: 4e75                      rts     

The rest of the code prepares the call to the protected version of Enqueue, copying the address of the node in a1 , the absolute address of MemList in a0, and branching to the subroutine. When Enqueue returns, AddMemList terminates and returns as well.

Resources

  • Motorola M68000 Family Programmer's Reference Manual PDF here
  • Amiga System Programmers Guide, Abacus (pdf here)

Feedback

Feel free to reach me on Twitter if you have questions. The GitHub issues page is the best place to submit corrections.