Linker 64 — Schluß mit dem Nachladen
Wenn Sie öfters mehrere Maschinenprogramme gleichzeitig im Speicher haben wollen und es leid sind, jedes Programm einzeln nachzuladen, dann ist der Linker das ideale Arbeitswerkzeug für Sie.
Seine Anwendungen sind vielfältig: vom einfachen Basic-Start-Generator für Maschinenprogramme bis zum Verketter (= Linker) von mehrteiligen Spielen. Das Endprodukt, also das generierte Programm, kann dann ganz einfach auch von der Datasette geladen werden, was bisher an den Diskettenladeroutinen der Programme scheiterte. Diese Laderoutinen müssen natürlich vorher entfernt werden.
Die Bedienung ist denkbar einfach: Diskette mit den einzelnen Programmen nehmen, Linker 64 (siehe Listing) starten und bedienen. Es wird eine lauffähige Version generiert, die man mit LOAD und RUN starten kann.
Die einzige Denkarbeit besteht in der Überlegung, in welcher Reihenfolge die Programme wieder an ihre ursprünglichen Adressen verschoben werden sollen. Das ist wichtig, da sonst während des Verschiebevorgangs bereits verschobene Programmteile, die noch nicht verschobenen überschreiben könnten. Im allgemeinen wird man zuerst die Maschinenroutinen verschieben, die an die höchste Adresse geschrieben werden sollen.
Die Arbeitsweise des Linkers
Der Linker generiert drei Programmteile, die am Ende verkettet werden: Teil »S« besteht aus der Basic-Zeilennummer, dem SYS-Befehl und dem anschließenden Text, gleich einer REM-Zeile. Teil »V« besteht aus dem generierten Verschiebeprogramm. Für Interessierte sei gesagt, daß bei einer Verschiebung nach oben die betriebssysteminterne Blockverschieberoutine ($a3bf) angesprungen wird. Bei Transfer nach unten kommt eine »handgestrickte« Routine zur Anwendung. Teil »P« enthält schließlich die aneinandergehängten Maschinenprogramme.
Der Linker kennt zwei Betriebsarten: entweder Verschiebeteil vor oder nach den gelinkten Programmen. Im Modus »0« ist die Reihenfolge der drei Teile S-V-P, im Modus »1« ist sie S-P-V. Der Modus »0« (LoMem-Modus) wird fast immer benutzt, da nursehrselten Programmteile im Basic-Startbereich (2049 = $0801) laufen. Sollte dies aber doch der Fall sein, oder sollen Basic-Programme mit Maschinenprogrammen zusammengelinkt werden, so muß Modus »1« (HiMem-Modus) verwendet werden. Die Verschieberoutine würde sich sonst selbst überschreiben.
Anwendung des Linkers
Beispiel: Wir wollen aus dem SMON $c000, einer RENEW-Routine mit der Startadresse 36 000 und einem Assembler ASS $9000 ein einziges Programm machen. Dieses sollte nach dem Laden die einzelnen Teile gleich an die richtigen Adressen im Speicher versetzen und mit »RUN« zu starten sein. Weiterhin soll danach gleich der SMON starten:
(Alle drei Programme müssen sich auf der gleichen Diskette befinden.) Zuerst lädt man den Linker und startet ihn. Dann gibt man die Zeilennummer, in der später der SYS-Befehl stehen soll, einen erklärenden REM-Text und den Namen ein, den das fertige Programm erhalten soll. Als Betriebsart wählen wir »0«, da der Basic-Startbereich von unseren Programmen nicht berührt wird. Die Frage nach der Anzahl der Programme beantworten wir mir »3«. Nun werdendie Namen der einzelnen Programme eingegeben, woraufhin der Linker deren Startadressen berechnet und ausgibt. Diese könnten jetzt noch geändert werden, was aber nicht sehr ratsam ist. Maschinenprogramme haben im allgemeinen nämlich die Eigenschaft, nur in dem Speicherbereich zu laufen, für den sie geschrieben wurden. In unserem Fall übernehmen wir also die Startadressen mit der RETURN-Taste.
Jetzt fragt der Linker nach der Reihenfolge, in der später die Programme verschoben werden sollen (ist in unserem Fall egal, da sich unsere Beispielprogramme nicht im geringsten gegenseitig stören). Der Linker meldet sich mit:
1 | = SMON $c000 |
2 | = ASS $9000 |
3 | = RENEW |
4 | = ENDE |
Wir tippen zum Beispiel »2« für die Verschiebung des Assemblers. Nun wird für ihn die Einsprungsart verlangt: EINSPRUNG: O = KEINER, 1=JSR, 2=JMP
Da nach der Verschiebung des Assemblers dieser nicht gleich gestartet werden soll, geben wir eine »0« ein. Es erscheint am Bildschirm:
1 | = SMON $c000 |
2 | = |
3 | = RENEW |
4 | = ENDE |
Sie sehen, der Assembler ist »verschwunden«. Der Linker wartet auf dasnächste Kommando. Wir geben »3« für die RENEW-Routine ein. Da diese ebenfalls nicht gleich gestartet werden soll, beantworten wir die nachfolgende Frage mit »0«. Dann verschieben wir mit »1« den SMON. Wir haben vorher gesagt, daß das fertige Programm diesen gleich starten sollte. Also Einsprungsart »2«. Die Einsprungsadresse, die der Linker daraufhin verlangt, ist 49152 (SMON).
Es wird noch einmal nach der Einsprungsart gefragt, da bei der Eingabe von »1« (JSR) noch weitere Einsprünge erfolgen könnten. Wir quittieren das Ganze mit »0« und das Abenteuer ist für uns erledigt.
Jetzt wirft der Linker die DOS-interne Copy-Routine an, die die vorher generierten Teile »V« und »S« auf der Diskette direkt aneinanderhängt. Nach einiger Zeit meldet sich der Computer mit READY, und der Link-Vorgang ist beendet. Auf der Diskette befindet sich jetzt das fertige Programm, das mit LOAD"name”,8 geladen und mit »RUN« gestartet werden kann. Es werden zuerst die gelinkten Programme in der festgelegten Reihenfolge an ihren Platz im Speicher verschoben und die durch JMP oder JSR definierten Adressen angesprungen. Achtung: Vor einem erneuten Start durch »RUN« sollte das Programm erst noch einmal geladen werden, ebenso sollte es nach dem Verschieben nicht mehr gespeichert werden.
Die wichtigsten Unterprogramme des Linkers sind in der Tabelle beschrieben. Die Röutine »Test auf Vorhandensein von Programmen auf Disk« eignet sich gut zum Einbau in eigene Programme.
(Andreas Knipp/tr)U$ | Überschrift |
T$ | REM-Text, der hinter dem SYS-Befehl im generierten Programm steht. |
NA(U) | Neue Anfangsadresse (Adresse, an der das Einzelprogramm nach dem Verschieben steht) |
NE(U) | Neue Endadresse |
S(U) | Alte Anfangsadresse (dort steht der Programmtell im fertigen Programm) |
E(U) | Alte Endadresse |
K | Anzahl der zu linkenden Programme (maximal acht) |
BS$ | Floppy-Befehlstring |
LH | LoMem- oder HiMem-Modus |
NA$ | Name des fertigen Programms |
Eingangsvariable | Ausgangsvariable | Beschreibung | Einsprung |
---|---|---|---|
A = 2 byte-Zahl | AL% (LO-Anteil) AH% (HI-Anteil) |
940 | |
X$ = filename | C1 = 0 vorhanden, 62 = nicht vorhanden |
Testet auf Vorhandensein von Programmen | 950 |
- | - | Fehlerbehandlung: Return nur bei ok oder File not found, sonst Abbruch. | 960 |
F$ = filename | SA = Startadresse, EA = Endadresse, V = Verbrauch |
Berechnet SA, EA, V und schreibt aktuellen Track und Sektor | 1120 |
10 rem ********************* 20 rem * linker 64 * 30 rem * (c) andreas knipp * 40 rem ********************* 50 poke53280,4:poke53281,5 60 u$="{clr}{orng}{rvon} {rvof}"+chr$(13) 70 u$=u$+"{wht} l i n k e r 6 4"+chr$(13) 80 u$=u$+" {down}programmed by andreas knipp"+chr$(13) 90 printu$ 100 open15,8,15,"i":gosub960 110 z=0:a$="":ll$=" "+chr$(13)+"{up}" 120 input"{home}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}zeilennummer ";zn 130 input"{down}text ";t$ 140 printll$zn;"sysRRRRR "t$ 150 input"{down}name des generierten prg";na$:x$=na$:gosub950 160 ifc1=0thenprint"{up}"na$" bereits vorhanden ":goto120 170 input"{down}wieviele prg werden verkettet ";k 180 print"{down}verschiebeprg vor (=0)" 190 input"oder nach (=1) programmblock";lh 200 input"{down}alles richtig j/n";ar$ 210 ifar$<>"j"then120 220 dimna(20),ne(20),s(20),e(20),n$(20),n1$(20) 230 z=2064+len(t$) 240 iflh=0thense=z:z=z+k*40:pa=z 250 iflhthenz=z+2 260 printu$ 270 foru=1tok 280 print"name des"u"{left}.prg":inputn$(u):n1$(u)=n$(u) 290 x$=n$(u):gosub950 300 ifc1thenprint"dises prg gibt es nicht":goto280 310 nextu 320 foru=1tok 330 f$=n$(u):gosub1120 340 print"{down}{down}startadresse von "n$(u):print"{rght}"sa:input"{up}";sa 350 ea=sa+v 360 na(u)=sa:ne(u)=ea:s(u)=z 370 e(u)=s(u)+v:z=e(u)+2 380 nextu 390 iflh=0thenz=se 400 iflh=1thenz=z-2:se=z 410 open3,8,3,"@:+v,p,w" 420 printu$"{down}{down}{down}{down}{down}" 430 fori=1tok:printi,n$(i):next:printk+1,"ende":n$(k+1)="~" 440 input"naechste prg-nummer";u:ifu>k+1orn$(u)=""then440 450 ifu=k+1then800 460 n$(u)="" 470 ife(u)=ne(u)goto740 480 ife(u)>ne(u)goto590 490 a=s(u):gosub940 500 print#3,chr$(169)chr$(al)chr$(133)chr$(95)chr$(169); 510 print#3,chr$(ah)chr$(133)chr$(96); 520 a=e(u)+1:gosub940 530 print#3,chr$(169)chr$(al)chr$(133)chr$(90)chr$(169); 540 print#3,chr$(ah)chr$(133)chr$(91); 550 a=ne(u)+1:gosub940 560 print#3,chr$(169)chr$(al)chr$(133)chr$(88)chr$(169); 570 print#3,chr$(ah)chr$(133)chr$(89); 580 print#3,chr$(32)chr$(191)chr$(163);:z=z+27:goto740 590 a=s(u):gosub940:a1=z+4 600 print#3,chr$(160)chr$(0)chr$(185)chr$(al)chr$(ah)chr$(153); 610 z=z+6:a=na(u):gosub940:a2=z+1 620 print#3,chr$(al)chr$(ah)chr$(200)+chr$(192);:a=ne(u)+1-al 630 gosub940:z=z+4 640 a3=ah 650 print#3,chr$(al)chr$(240)chr$(12)chr$(152)chr$(208); 660 print#3,chr$(242)chr$(238); 670 z=z+7:a=a1:gosub940 680 print#3,chr$(al)chr$(ah)chr$(238); 690 z=z+3:a=a2:gosub940 700 print#3,chr$(al)chr$(ah)chr$(24)chr$(144)chr$(233); 710 print#3,chr$(173)chr$(al)chr$(ah); 720 z=z+8 730 print#3,chr$(201)chr$(a3)chr$(144)chr$(237);:z=z+4 740 input"einsprung: 0=keiner,1=jsr,2=jmp";e:ife<0ore>2then740 750 ife=1ore=2theninput"einsprung";a:gosub940:ifint(ah/256)goto750 760 ife=2thenprint#3,chr$(76)chr$(al)chr$(ah);:z=z+3 770 ife=1thenprint#3,chr$(32)chr$(al)chr$(ah);:z=z+3 780 ife<>0then740 790 goto420 800 iflh=0thenforu=(z+1)to(pa-2):print#3,chr$(rnd(u)*255);:next 810 close3 820 sa$=mid$(str$(se)+" ",2,5) 830 open2,8,2,"@:+s,p,w":gosub960 840 a=zn:gosub940 850 print#2,chr$(1)chr$(8)chr$(13+len(t$))chr$(8); 860 print#2,chr$(al); 870 print#2,chr$(ah); 880 print#2,chr$(158); 890 print#2,sa$+" "+t$; 900 fori=1to4 910 print#2,chr$(0);:next:close2 920 iflhthenprint#15,"r:Q=+s":gosub960:goto1000 930 print#15,"c:Q=+s,+v":gosub960:goto1000 940 ah=int(a/256):al=a-ah*256:return 950 open6,8,6,x$:close6 960 input#15,c1,c$,c2,c3:ifc1=0orc1=62thenreturn 970 printc1,c$,c2,c3 980 print"programmabbruch!!!!" 990 close15:sys65511:open1,8,15,"s:+?":close1:end 1000 fori=1tok:n$(i)=n1$(i) 1010 n1$(i)="+"+right$(str$(i),1):print#15,"r:"+n1$(i)+"="+n$(i):next 1020 bs$="c:W=Q," 1030 fori=1tok 1040 bs$=bs$+n1$(i) 1050 ifi=kthen1070 1060 bs$=bs$+"," 1070 next 1080 iflhthenbs$=bs$+",+v" 1090 print#15,bs$:gosub960:print#15,"s:Q" 1100 fori=1tok:print#15,"r:"+n$(i)+"="+n1$(i):next 1110 print#15,"r:"+na$+"=W":goto990 1120 open2,8,2,"#" 1130 t=18:s=1 1140 print#15,"u1";2;0;t;s 1150 print#15,"b-p";2;0 1160 gosub1440:t=a 1170 gosub1440:s=a 1180 forx=0to7 1190 print#15,"b-p";2;x*32+3 1200 gosub1440:t1=a 1210 gosub1440:s1=a 1220 ff$="" 1230 fory=1to16 1240 gosub1440 1250 ifff$=f$then1310 1260 ff$=ff$+a$ 1270 ifa$<>mid$(f$,y,1)theny=16 1280 nexty 1290 nextx 1300 goto1140 1310 printu$"{down}{down}{down}in verarbeitung:{rvon}"+f$+"{rvof}" 1320 t=t1:s=s1 1330 print#15,"u1";2;0;t;s:printll$;t,s:print"{up}"; 1340 print#15,"b-p";2;0 1350 gosub1440:t=a 1360 gosub1440:s=a 1370 ifq=0thenq=1:gosub1440:sl=a:gosub1440:sh=a:sa=sl+sh*256 1380 ift=0thenea=sa+pz*254+s-3:v=ea-sa:goto1400 1390 pz=pz+1:goto1330 1400 print"{down}startadresse:"sa 1410 print"endadresse :"ea 1420 print"verbrauch :"v 1430 close2:q=0:pz=0:return 1440 get#2,a$:ifa$=""thena$=chr$(0) 1450 a=asc(a$):return