Chapter 28
A program in several modules
This chapter builds TWOMOD, the example in A:\TATARA\EXAMPLES\TWOMOD: a program of two modules, linked from a link file. It goes through the files one at a time, builds the program, looks at what TANREN made of it, and then changes it in three ways to show what goes wrong, and why. The reference chapters named along the way describe each part in full.
28.1 The example
TWOMOD prints two lines and ends:
A:\TATARA\EXAMPLES\TWOMOD>twomod Two modules, one program. The linker joined them.
The printing is done by a routine, putstr, that is in a module of its own. The example has four files:
- MAIN.AS, the module where the program starts;
- PUTSTR.AS, the module with putstr;
- TWOMOD.LNK, the link file that names the two modules;
- BUILD.BAT, a batch file that assembles and links them.
28.2 The two modules
MAIN.AS calls putstr twice, once for each line, and then ends the program:
; MAIN.AS - one of two modules. ; ; It knows that putstr exists. It does not know where putstr is, and ; it does not need to: EXTRN says the name is somebody else's, and the ; linker fills the address in. extrn putstr ; defined in PUTSTR.AS include msxdos.inc ; _TERM0 and "system" include ascii.inc ; CHR_CR and CHR_LF cseg start: ld hl,msg1 call putstr ld hl,msg2 call putstr system _TERM0 msg1: db "Two modules, one program.",CHR_CR,CHR_LF,0 msg2: db "The linker joined them.",CHR_CR,CHR_LF,0 end start
extrn putstr tells TATARA that putstr is defined in some other module (section 12.5). TATARA does not know where, so it leaves the address in each call putstr to be filled in by TANREN. The END line names start, the first instruction.
PUTSTR.AS defines putstr, which prints the characters of a string one at a time until it reaches a zero byte:
; PUTSTR.AS - the other module. ; ; PUBLIC is what makes the name visible outside this file. Without it ; the name would still assemble here and the link of MAIN.AS would ; stop with an undefined symbol - which is worth trying once. public putstr include msxdos.inc ; BDOS, _CONOUT and "system" cseg ; putstr - print a string. ; ; HL is pushed around the BDOS call because MSX-DOS promises nothing ; about the registers it hands back. ; ; Input: HL -> the string, ending in a zero byte ; Output: it is printed ; Modifies: AF, BC, DE, HL putstr: ld a,(hl) or a ret z push hl ld e,a system _CONOUT pop hl inc hl jr putstr end
public putstr offers the name to the other modules. Neither module knows anything else about the other: MAIN.AS knows only the name putstr, and PUTSTR.AS does not know who calls it. Each is assembled on its own, into an object file of its own.
28.3 The link file
TWOMOD.LNK holds the rest of the link (chapter 22):
; TWOMOD.LNK - the link, in a file instead of on the command line. ; ; TANREN reads this when it is given @TWOMOD, and the words in it are ; the words it would have read from the command line. A ; starts a ; comment and the rest of the line is ignored. ; ; MSX-DOS gives a command line 127 characters and cuts a longer one ; without saying so. That is the whole reason this exists: the ; assembler is itself linked from a file of nineteen module names. /o:twomod.com main.tro putstr.tro
After the comment come two lines of words: /o:twomod.com, the name of the output file, and the two object files. MAIN.TRO is named first, because MSX-DOS starts a .COM program at 0100h, and TANREN puts the code of the first module there (section 21.1).
28.4 Building and running it
BUILD.BAT makes the program:
rem BUILD.BAT - EXAMPLE: TWOMOD rem rem Two modules, one program. MAIN.AS declares putstr EXTRN and rem PUTSTR.AS declares it PUBLIC; the linker matches the two. rem rem The link itself is in TWOMOD.LNK rather than on this line. @NAME rem reads a file of words, and .LNK is assumed when the name has no rem extension. Everything in it could have been typed here instead. rem The headers are in A:\TATARA\INCLUDE and are not copied here. rem TATARA is rule 3 of the include search - see the DIRS example. rem CHANGE THE PATH BELOW if you put the tree somewhere else. set TATARA=a:\tatara\include echo === Assembling both modules tatara /q main.as main.tro tatara /q putstr.as putstr.tro echo === Linking from TWOMOD.LNK tanren /q @twomod echo === Done. Type TWOMOD to run it. set TATARA=
It sets TATARA, so that TATARA finds MSXDOS.INC and ASCII.INC (section 13.3), assembles each module into its object file, links them with @twomod, and clears TATARA again. Figure 28.1 shows the files it reads and makes.
Run it from the example’s directory. /Q keeps TATARA and TANREN quiet, so only the batch file’s own lines are printed:
A:\TATARA\EXAMPLES\TWOMOD>build === Assembling both modules === Linking from TWOMOD.LNK === Done. Type TWOMOD to run it. A:\TATARA\EXAMPLES\TWOMOD>twomod Two modules, one program. The linker joined them.
The same link without /Q shows TANREN’s summary. It needs TATARA only for assembling, so this link can be typed as it is:
A:\TATARA\EXAMPLES\TWOMOD>tanren @twomod Tatara MSX Linker v1.2.0 Copyright (C) 2026 Javier Lavandeira https://tatara.tools 2 modules, 14 records, ends at EOF. Wrote TWOMOD.COM, 0100-0154 (85 bytes), entry 0100.
28.5 What TANREN did
/M shows where TANREN put everything (section 25.7):
A:\TATARA\EXAMPLES\TWOMOD>tanren /m @twomod /o:tm.com ... Modules: 2 Groups: Segments: flags 00 group FF base 0100 size 0055 end 0154 C flags 01 group FF base 0155 size 0000 end ----- D Symbols: DR 0147 putstr 2 modules, 14 records, ends at EOF. Wrote TM.COM, 0100-0154 (85 bytes), entry 0100.
Both modules put everything in the default code segment, so the program is one code segment of 55h bytes, from 0100h to 0154h: the 71 bytes of MAIN first, then the 14 of PUTSTR. The data segment is empty. putstr is defined (D) by PUTSTR, referenced (R) by MAIN, and at 0147h, the first byte after MAIN. TANREN wrote 0147h into both call putstr instructions (figure 28.2).
28.6 Things to try
Each change below shows one of the mistakes that TANREN finds. The files it uses are not part of the example: make them as copies of its files, in the same directory. The assembling needs TATARA, as in BUILD.BAT:
set tatara=a:\tatara\include
28.6.1 Leave out PUBLIC
NOPUB.AS is PUTSTR.AS without its public putstr line. It still assembles: putstr is an ordinary label in it. But no module offers the name any more, and the link fails (section 25.3):
A:\TATARA\EXAMPLES\TWOMOD>tatara /q nopub.as nopub.tro A:\TATARA\EXAMPLES\TWOMOD>tanren main nopub /o:np.com ... Never defined: putstr ERROR: the symbols above were never defined.
28.6.2 Name the modules the other way round
SWAP.LNK is TWOMOD.LNK with putstr.tro main.tro on its last line, and /o:swap.com. It links without an error, but with a warning:
A:\TATARA\EXAMPLES\TWOMOD>tanren /m @swap ... Symbols: DR 0100 putstr WARNING: SWAP.COM is entered at 0100, not at 010E. 2 modules, 14 records, ends at EOF. Wrote SWAP.COM, 0100-0154 (85 bytes), entry 010E.
But putstr is now at 0100h, and start at 010Eh. MSX-DOS starts SWAP.COM at 0100h, so it runs putstr with whatever HL happens to hold, and never reaches the code of MAIN. Do not run it. The entry address, 010Eh, is right, but a .COM program cannot start there, and TANREN says so in its warning (section 26.5).
28.6.3 Assemble one module with /C
MAIN.AS assembled with /C, and PUTSTR.AS without it, cannot be linked together (section 25.5):
A:\TATARA\EXAMPLES\TWOMOD>tatara /q /c main.as mainc.tro A:\TATARA\EXAMPLES\TWOMOD>tanren mainc putstr /o:mix.com ... ERROR: one module was assembled /C and another was not.