8. Linker

The linker is used to combine the output of the assembler and the RPN compiler into a module file.

The linker is a command line tool named lnnut. Running it from the command line with the ``–help`` option will give a sign-on message and display accepted options:

Calypsi linker for Hewlett-Packard Nut version 5.16

Usage: lnnut [--version] [-o|--output-file OUTPUT-FILE] [-g|--debug]
             [--semi-hosted] [--no-data-init-table-section]
             [--override IDENTIFIER] ([--cross-reference] |
             [--no-cross-reference]) [--rtattr NAME=VALUE] [--verbose] [-l]
             [--list-file LIST-FILE] [--memories-expression EXPRESSION]
             [--program-root SYMBOL] [--root-symbol SYMBOL]
             [--program-start SYMBOL] [--copy-initialize SECTION]
             [--no-copy-initialize SECTION] [--no-automatic-placement-rules]
             [--raw-multiple-memories] [--no-merge-raw-memories]
             [--force-output] [--no-auto-libraries] [--cstartup VALUE]
             [--no-tree-shaking] [--output-format FORMAT]
             [--extra-output-formats FORMAT] [FILE...]
  use 'lnnut --help' for detailed help

Available options:
  --version                Display version number
  -o,--output-file OUTPUT-FILE
                           Name of output file
  -g,--debug               Produce debugging information
  --semi-hosted            Enable debug stubs for semi-hosting
  --no-data-init-table-section
                           Do not generate any data_init_table section (mainly
                           useful for assembly projects)
  --override IDENTIFIER    Override symbol in archive library (treat it as weak)
  --cross-reference        Include cross reference and map information in the
                           list file (this is the default)
  --no-cross-reference     Do not include cross reference and map information in
                           the list file
  --rtattr NAME=VALUE      Specify runtime attribute to select specific
                           alternative from library (e.g. printf=float,
                           scanf=nofloat)
  --verbose                Generate more detailed output
  -l                       Generate a list file, defaults to 'lnnut.lst'
  --list-file LIST-FILE    Generate list file, using given name
  --memories-expression EXPRESSION
                           Expression that extracts the list of memory
                           descriptions from the .scm file, defaults to
                           'memories'
  --program-root SYMBOL    Program root point, defaults to
                           '__program_root_section'
  --root-symbol SYMBOL     Add a root symbol
  --program-start SYMBOL   Program start symbol, defaults to '__program_start'
  --copy-initialize SECTION
                           Override default and initialize this section by
                           copying
  --no-copy-initialize SECTION
                           Override default and do not initialize this section
                           by copying
  --no-automatic-placement-rules
                           Do not add rules to a .scm linker rules file
  --raw-multiple-memories  Allow multiple memories for raw style output format
  --no-merge-raw-memories  Never attempt to merge raw memories
  --force-output           Ignore (some) errors and attempt to generate output
  --no-auto-libraries      Do not automatically add runtime libraries
  --cstartup VALUE         Define the C startup to be used, synonym to '--rtattr
                           cstartup=VALUE'
  --no-tree-shaking        Do not perform tree shaking and keep unused section
                           fragment in output
  --output-format FORMAT   Format, one of 'MOD1' or 'MOD2', defaults to 'MOD1'
                           (in addition to the ELF/DWARF output)
  --extra-output-formats FORMAT
                           Format, only accepted argument is 'MOD2'
  -h,--help                Show this help text

You need to specify the object files, a module description file and a linker rules file as input.

From each object file comes named relocatable section fragments that are to be placed at a final position in some memory. The link process is defined by a set of rules that govern where section fragments can be placed.

8.1. Memory

A memory is a named entity that gives a continuous range of storage locations. On the HP-41, a 4K page is normally treated as a memory. A description of a memory contains the following attributes:

name

The name of the memory. The memory name is the identifier used for a particular memory area

address range

The start and end address (inclusive) specify the address range of the memory.

position

Tells whether the memory is fixed or position independent. For a page relocatable module you want to specify this as (position independent). The alternative is (position fixed) if you are writing code for a fixed address.

section

Here you put the section names you want to bind to the current memory.

checksum

An optional address where a checksum will be stored. For the HP-41 it should be the highest address of a 4K page (last address in memory). You should also specify the checksum algorithm to be used. The normal value to use here is (checksum #xFFF hp41).

fill word

Value used to fill unused locations in the memory. Defaults to zero, but can specified as (fill 0).

8.2. Linker rules file

The rules file (extension .scm) describes how the section fragments are to be laid out in memory. This file serves three purposes, a) it defines the actual memories; b) it defines which sections to expect; and c) it describes where the sections can be placed in memory.

Memory rule

A memory rule defines a memory. It also defines which sections (by name) that will go into that memory. There can be multiple memory rules in a rule file and you will need one for each memory block.

A simple rule file for a relocatable 4K module where you have used a single section CODE may look as:

(define memories
  '((memory Page1 (address (#x0 . #xFFF) (position independent))
            (section CODE)
            (checksum #xFFF hp41)
            (fill 0))))

While the use of spacing for indentation here is unimportant to the linker tool, it makes the file easier to read. However, the use of characters like the single quote, period and parentheses are important to make the linker tool able to parse the file correctly.

A slightly more advanced example with a 8K relocatable module with some sections:

(define memories
  '((memory Page1 (address (#x0 . #xFFF)) (position independent)
            (section (FAT1 #x0) Code1)
            (checksum #xFFF hp41)
            (fill 0))
    (memory Page2 (address (#x1000 . #x1FFF)) (position independent)
            (section (FAT2 #0) Code2)
            (checksum #x1FFF hp41)
            (fill 0))))

Section placement

There are several ways to control how sections are placed. Here we walk through some typical cases, consider this linker rules file:

(define memories
  '((memory Page (position independent)
            (bank 1) (address (#x0 . #xFFF))
            (section (ID FAT FATEND #x0)
                     (CodeQ0 (#x0 . #x3FF))
                     Code1 Code2
                     (Tail #xFF4))
            (checksum #xFFF hp41)
            (fill 0))))

Free placement

The Code1 and Code2 sections appear alone (no surrounding parentheses). Sections with these names can be placed anywhere in the defined memory.

Restricted placement

The CodeQ0 section has an address range, which should be a sub-range of the memory range. It can be placed anywhere within the sub-range.

Fixed placement

The Tail section will be placed starting at address FF4.

Fixed section group placement

A FAT (function address table) consists of three parts. First an identifier, then a table of functions and finally an end marker. The section group ID, FAT and FATEND are placed together starting at address 0. The order in which section fragments are placed are important when setting up a FAT, as this also gives the XROM number allocation.

In this example section fragments are placed so that all ID fragments comes first, followed by all FAT fragments and finally the FATEND fragments.

In a real setup, ID and FATEND probably consist of single section fragments. However, the FAT section is likely to consist of multiple section fragments.

The order in which FAT section fragments are placed here, is the same as the order in which the object files appear on the command line to the linker. If there are multiple FAT sections within the same object file, the order among them are decided by the order in which they were encountered when compiling the source file.

Note

You will most likely want to ensure consistent ordering in the FAT, as the actual XROM number allocated for each function depends on it.

Banking

To make a module with banked pages, 1 you place more than one memory at the same address, but with different bank numbers using the bank attribute.

The following rule file shows how to use the bank attribute for banked memory:

(define memories
  '((memory CodeBank1 (address (#x0 . #xFFF) (position independent))
            (section FAT CodeBank1)
            (checksum #xFFF sum-carry)
            (bank 1)
            (fill 0))
    (memory CodeBank2 (address (#x000 . #xFFF) (position independent))
            (section CodeBank2)
            (checksum #xFFF sum-carry)
            (bank 2)
            (fill 0))))

These bank numbers are only used for detecting problems when resolving external references at the link stage.

The actual bank and page layout of the module is described in the module description file (.moddesc). The memories in the linker control file are paired with pages in the module description file based on their names. The meta data of the module description file is combined with the memory images to form the final module image.

8.3. Linker list files

The linker list file that can be optionally generated shows how the section fragments are distributed in the memories and the total size of memory allocated.

###############################################################################
#                                                                             #
# Calypsi linker for Hewlett-Packard Nut                         version 5.16 #
#                                                       14/Apr/2026  16:42:38 #
# Command line: -l poker.o roulette.o header.o PokerSupport.o Plugin4K.scm    #
#               games.moddesc -o games.mod                                    #
#                                                                             #
###############################################################################

####################
#                  #
# Memories summary #
#                  #
####################

Name Range     Size    Used    Checksum  Largest unallocated
------------------------------------------------------------
Page 0000-0fff 1000     45.1%  136       08c6


####################
#                  #
# Sections summary #
#                  #
####################

Name   Range      Size    Memory Fragments
------------------------------------------
HEADER 0000-0003  0004    Page   1
FAT    0004-000d  000a    Page   3
FATEND 000e-000f  0002    Page   1
RPN    0010-037f  0370    Page   1
Code   0380-0572  01f3    Page   1
RPN    0573-0720  01ae    Page   1
Code   0721-072d  000d    Page   1
TAIL   0ff4-0ffe  000b    Page   1


###########################
#                         #
# XROM allocation summary #
#                         #
###########################

XROM      Address   Kind        Function
------------------------------------------------
21,00     872d      MCODE       -GAMES EX 1A
21,01     8549      MCODE       CARD
21,02     8528      MCODE       MV
21,03     8383      MCODE       TRI
21,04     8575      RPN         "ROULETT"
21,05     8012      RPN         "POKER+"


################
#              #
# Object files #
#              #
################

Unit Filename       Archive
--------------------
  0  poker.o        -
          >  FAT 0002
          >  RPN 0370
  1  roulette.o     -
          >  FAT 0002
          >  RPN 01ae
  2  header.o       -
          >  Code   000d
          >  FATEND 0002
          >  HEADER 0004
          >  TAIL   000b
  3  PokerSupport.o -
          >  Code 01f3
          >  FAT  0006

###################
#                 #
# Cross reference #
#                 #
###################

Section 'HEADER'  placed at address 0000-0003 of size 0004
(header.o unit 2 section index 2)

`FAT entry: CARD` in section 'FAT'  placed at address 0004-0009 of size 0006
(PokerSupport.o unit 3 section index 2)
    Defines:
        `FAT entry: TRI` = 10000008
        `FAT entry: MV` = 10000006
        `FAT entry: CARD` = 10000004
    Referenced from:
        (poker.o unit 0 section index 3)

`FAT entry: ROULETT` in section 'FAT'  placed at address 000a-000b of size 0002
(roulette.o unit 1 section index 2)
    Defines:
        `FAT entry: ROULETT` = 1000000a

`FAT entry: POKER+` in section 'FAT'  placed at address 000c-000d of size 0002
(poker.o unit 0 section index 2)
    Defines:
        `FAT entry: POKER+` = 1000000c

fatend in section 'FATEND'  placed at address 000e-000f of size 0002
(header.o unit 2 section index 3)

`POKER+` in section 'RPN'  placed at address 0010-037f of size 0370
(poker.o unit 0 section index 3)
    References:
        `FAT entry: CARD` in (PokerSupport.o unit 3 section index 2)
        `FAT entry: MV` in (PokerSupport.o unit 3 section index 2)
        `FAT entry: TRI` in (PokerSupport.o unit 3 section index 2)

entry_TRI in section 'Code'  placed at address 0380-0572 of size 01f3
(PokerSupport.o unit 3 section index 3)

ROULETT in section 'RPN'  placed at address 0573-0720 of size 01ae
(roulette.o unit 1 section index 3)

header in section 'Code'  placed at address 0721-072d of size 000d
(header.o unit 2 section index 4)

Section 'TAIL'  placed at address 0ff4-0ffe of size 000b
(header.o unit 2 section index 5)

##########################
#                        #
# Memory sizes (decimal) #
#                        #
##########################

Executable  (Text): 1849 words

The Memories summary section summarizes the different memories (pages) used in the module. Here you will see the size of the memory, how much memory is in use and the largest unallocated continuous memory block. The checksum column lists the calculated checksum, which is also saved in the memory image.

The Sections summary gives an idea of where the sections are located and how many section fragments there are for each one.

The XROM allocation summary lists the FAT and gives the entry point for each routine (using some fictive page address if the page is relocatable).

Finally the Memory sizes summarizes the total amount of memory in use. Note that the amount of memory is given in decimal (addresses and sizes above are in hexadecimal).

8.4. Module files

Output from the linker is in a binary format called module file. This is a special binary format defined for the HP-41 which is inspired by how actual modules looks. A one single file is used to describe a plug-in module. Multiple 4K pages can reside in a module and they can either be loaded to a fixed addresses, being relocatable and even have multiple pages that are to be relocated together. It can also describe banked 4K pages and tell whether the module contains special hardware.

To create a module file, a module description file is needed. This contains the meta data of the module file.

The easiest way to create a .moddesc file is to use the module tool modtool to extract one from an existing .mod file that have similarities with the module you are going to create (see Module description file).

8.5. More on linker rules

The linker rules file is actually a source file for the Scheme programming language, which is the reason for the choice of the .scm file extension.

You do not need to be familiar with Scheme to specify linker rules, simply follow the examples to set things up.

The shown examples are just a Scheme “program” that consists of a single variable named memories which is bound to a data structure. The linker runs a Scheme interpreter to read the file and then look for the resulting memories variable and peek into its contents. This has two implications, a) the syntax of the file is dictated by Scheme, which is based on s-expressions; and b) it is possible to use a more elaborate program with Scheme macros to generate the memory rules.

Footnotes

1

This means that more than one page are at the same address, though only one will be visible to the microprocessor at a given time.