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.