Das Problem der Computerviren ist leidig bekannt; Lösungsansätze kommen vor allem in Form von speziellen Antivirenprogrammen. Diese sind jedoch für die Benutzer nicht immer einfach zu handhaben. Bei der Installation oder Benutzung ist häufig Vorwissen erforderlich (Link-Virus? Boot-Sektor? Interrupt? Vektor-Überprüfung?). Zudem muß sich der Benutzer in großer Selbstdisziplin üben; eine Diskette will immer zuerst getestet (neudeutsch: gescannt) sein. All dies sind Dinge, die einen Anfänger überfordern können und wohl auch von fortgeschrittenen Anwendern nicht immer eingehalten werden.
Die Forderung liegt daher nahe, daß die Virenabwehr bereits beim (hoffentlich) sachverständigen Entwickler und nicht erst beim Endanwender einsetzen sollte. Hier wird deshalb ein Verfahren vorgestellt, mit dem Programme so geschrieben werden können, daß sie sich selbständig auf Virenbefall testen.
Man überlege sich einmal folgendes: Ein Virus (genauer ein Link-Virus; und nur um diese geht es hier) muß auf jeden Fall, so geschickt und ,intelligent* er auch sein mag, das von ihm befallene Programm irgendwo verändern. Wenn also über den gesamten ausführbaren Maschinencode beim ersten Aufstarten eine Prüfsumme gebildet wird, kann durch Vergleich des jedesmal neu berechneten Wertes mit der ersten Prüfsumme festgestellt werden, ob ein Virus an einem Programm herumgebastelt hat.
Dem, was sich hier so einfach anhört, steht in der praktischen Realisierung ein Problem gegenüber: Der Code im Speicher ist bei jedem neuen Start anders, da das Betriebssystem des ATARI ein Programm an eine beliebige Stelle laden kann. Damit es dann dort auch läuft, wird es reloziert, das heißt, sämtliche absoluten Sprünge und Bezüge werden umgerechnet. Dadurch verändern sich aber viele Stellen. Wie dieses Problem gelöst werden kann, wird später besprochen. Eine (zu) einfache Lösung wäre es, die Programmdatei einfach nochmals vom Medium einzulesen. Dies ist aber aus verschiedenen Gründen unsinnig (unnötige Verzögerung zweimaliges Lesen; durch Viren einfacher abzulenken als direkte Überprüfung im Speicher).
Die Idee ist natürlich nicht grundlegend neu; darauf gestoßen bin ich durch einen Artikel in einer Fachzeitschrift ([1]). Die dort vorgestellten Routinen sind aber nur für MS-DOS-Systeme zu gebrauchen, deshalb schrieb ich eine Adaptierung für TOS.
All jenen Leser, welche sich nicht weiter für die internen Details der Realisierung interessieren, soll in diesem Abschnitt gezeigt werden, wie sie ihre Programme einfach schützen können. Als Vorlage für das Vorgehen kann man sich ans Beispielprogramm DEMO.C halten.
Im Hauptteil des Programms braucht man keinerlei Rücksicht auf die Virus-Check-Funktion zu nehmen. Nur in der Initialisierungsphase muß irgendwann einmal die Prüfsummenfunktion aufgerufen werden. Wenn man ein bestehendes Projekt schützen will, sollte man daher so Vorgehen:
Ein negatives Vorzeichen des Rückgabewertes zeigt an, daß eine Programmänderung vorliegt (Virusinfektion!), bei positivem Rückgabewert liegt ein Problem vor, aufgrund dessen die Prüfsumme nicht berechnet werden kann. Die genaue Bedeutung kann untenstehender Tabelle entnommen werden:
-4 | Prüfsumme stimmt nicht mehr, Programmcode wurde verändert |
-3 | Länge der Programmdatei oder eines Segments hat sich verändert |
-2 | Informationen in Basepage und Programm-Header stimmen nicht überein |
0 | alles OK, entweder Prüfsumme erstmalig berechnet und erfolgreich gespeichert, oder Prüfsumme stimmt mit erster Berechnung überein |
1 | Programmdatei konnte nicht für nur Lese- (beim normalen Test) oder Lese- und Schreibzugriff (beim ersten Starten) geöffnet werden; wahrscheinlich SaPrProgName falsch gesetzt |
2 | Prüfsumme kann wegen GEM-DOS-Fehler nicht in Programmdatei geschrieben werden |
In DEMO.C ist der englische Wortlaut dieser Fehlermeldungen im String-Feld err[] gespeichert, um nach dem Aufruf von SaPrSelfTest ausgegeben zu werden. In eigenen Programmen sollte jedoch eine Überprüfung auf ungleich Null genügen. Falls ein Fehler auftrat, sollte der Benutzer mit einer Meldung im Stil von "Dieses Programm wurde verändert. Wahrscheinlich liegt eine Virusinfektion vor!" gewarnt werden.
Im Prinzip braucht sich der Programmierer bei dieser Lösung nicht darum zu kümmern, ob die Prüfroutine nun zum ersten Mal benutzt wird oder ob es bereits der einmillionste Durchlauf ist (wie wär’s mit einer Meldung "Gratuliere & herzlichen Dank für Ihre Treue! Sie haben dieses Programm gerade zum 106 Mal gestartet"...). Falls man dies aber trotzdem in Erfahrung bringen will, muß nur getestet werden, ob die Prüfsumme noch gleich Null ist (also noch nie berechnet wurde) oder einen anderen Wert hat. Da dies in einer lokalen Variable des Moduls COC.C gespeichert ist, muß dem Compiler mit extern long SaPrLongs[2]; erst einmal klargemacht werden, daß man gedenkt, auf diese Variable zuzugreifen. Wenn SaPrLongs[0] dann gleich Null ist, handelt es sich um den ersten Aufruf des Programms.
Zum Schluß hier noch ein allgemeiner Tip (kann für alle in Module aufgeteilten Projekte benutzt werden), um die Turn-Around-Zeit des Compilers zu beschleunigen: Einmal compilierte Module liegen im Output-Verzeichnis (sehen Sie unter Options/Compiler... /Output directory nach, wo dies bei Ihnen liegt) als Objektdateien (*.O) vor. Diese können direkt in die Projektdatei eingebunden werden (wie dies mit dem Startupcode PCSTART.O ja immer geschieht). Compilieren Sie also COC.C einmal mit Compile und kopieren Sie COC.O dann ins BibliotheksVerzeichnis (Options/Linker.../Library directory), um in den Projektdateien COC.O statt COC.C für schnellere Compilierung verwenden zu können.
Wie arbeitet SaPrSelfTest() denn nun? Wie oben bereits angesprochen, kann nicht einfach eine Prüfsumme über das Programm im Speicher gebildet werden. Das Betriebssystem (genauer: das GEMDOS) reloziert ein Programm nämlich beim Laden. Darunter ist folgendes zu verstehen: Wenn (in Maschinencode) im Programm steht: ,lade die Variable aus Adresse 1000 in ein Prozessorregister', läuft das natürlich nur so lange gut, wie die gewünschte Variable wirklich an Speicherstelle 1000 steht. Da dorthin aber vielleicht bereits ein anderes Programm geladen wurde, geht unser Computer viel geschickter vor: Der Compiler/Linker sagt nur ,lade die Variable aus (Programmanfang+734 Bytes) in ein Register1. Beim Einladen sorgt dann das Betriebssystem durch Relozieren dafür, daß unser Programm an der betreffenden Stelle geändert wird, indem zur relativen Adresse der Variable die Anfangsadresse des Programms addiert wird.
Wo diese Umrechnungen vorgenommen werden müssen, entnimmt GEMDOS einer speziellen Tabelle, der Relozierungstabelle, die auch in der Programmdatei enthalten ist.
Mit diesem Wissen kann man eine Prüfsummenroutine schreiben, die diese Relozierungen wieder rückgängig macht. Dies darf natürlich nicht im Programmcode direkt geschehen, ein Puffer tut’s aber genauso.
Die Abschnitte zwischen den Relozierungspunkten kann man in einer einfachen Schleife durchlaufen. COC arbeitet also ungefähr so wie in dem nebenstehenden Flußdiagramm beschrieben.
Die Abbruchbedingung wurde hier der Einfachheit halber weggelassen; Schluß ist einfach, wenn HowManyBytesToTest (Konstante, in COC.C definiert) Bytes getestet wurden. Es darf aber nie zuviel überprüft werden, maximal (und das ist gleichzeitig die sinnvollste Variante, deshalb im Listing so gewählt) das ganze Textsegment (der Maschinencode des ausführbaren Programms). Dahinter kommt das Datensegment (Initialisierungswerte für Variablen), das sich aber sehr wohl ändern darf und daher nicht in die Prüfsummenberechnung miteinbezogen werden sollte.
Hier ist immer von Prüfsummenbildung die Rede. Wie geschieht das genau? Nun, eigentlich werden zwei Check-Summen benutzt; einmal eine fortlaufende Exklusiv-Oder-Verknüpfung und parallel dazu eine Addition ohne Überlauf. Wie man beim Studium des Flußdiagramms wohl schon geahnt hat, wird in Longword-Schritten gearbeitet. Deshalb wird auch bloß bis ,knapp vor1 dem nächsten Relozierungspunkt die Prüfsumme gebildet; soweit wie es mit Longwords nämlich gerade noch geht.
Wie man aus der Tabelle der Fehlermeldungen oben ersehen kann, werden auch noch andere Dinge überprüft. Wenn man den Programm-Header schon mal eingelesen hat, könnte man die Länge der Segmente sowie der ganzen Programmdatei gleich auch speichern (SaPrLens[4]) und vergleichen. Zusätzlich wird noch kontrolliert, ob die Angaben über die Segmentlängen in Basepage und Header übereinstimmen.
Die Routine ist relativ sicher programmiert und sollte sich nicht so ohne weiteres im Programm-Nirwana verlaufen (sprich: abstürzen). Es gibt jedoch einen Fall, der nicht abgefangen wird: Offsets für die nächste Relozierung dürf(t)en niemals kleiner als 4 sein (siehe dazu Erklärung der Relozierungsinformationen in [2]), macht ja eigentlich auch keinen Sinn. Zu meiner größten Überraschung fanden sich jedoch in vielen größeren (kommerziellen) Programmdateien 2er-Offsets. Meine PureC- und PurePascal-compilierten Projekte haben diese Eigenheit jedoch nie. Falls solche Offsets auftreten, ist das Verhalten der Prüfsummenfunktion Undefiniert.
Ebenfalls nicht abgefangen werden Programmdateien gänzlich ohne Relozierungstabelle (ph_absflag im Programm-Header !=0). Gemäß den Empfehlungen von ATARI sollte in diesem Fall aber sowieso eine leere Relozierungstabelle verwendet werden (erster Offset gleich 0L, siehe auch [2]). Offensichtlich gibt es jedoch Programme, welche diese Empfehlung mißachten (z.B. KSPREAD4 und UVK_5_3; vielleicht vollständig in Assembler programmiert).
Um derartige "faule Eier" aufzuspüren, gibt es das Programm CHECK, das die Relozierungsinformationen einer ausführbaren Datei auf Herz und Nieren auseinandernimmt. Es ist aus Platzgründen nicht abgedruckt; auf der Monatsdiskette ist es jedoch enthalten.
... einer Variable im Datensegment. Praktisch als Nebenprodukt fällt bei dies Routine ein nützlicher Trick ab: Die Prüfsumme und die Längeninformationen werden ja nicht in einer separaten Datei gespeichert (erstens würde es sich doch nicht lohnen, wegen 24 Bytes einen ganzen Cluster zu ,verbraten', und zweitens ist die folgende Lösung einfach eleganter), sondern direkt ins Datensegment der Programmdatei geschrieben. Auf dem Mac werden Programmeinstellungen schon längst so gespeichert, Windows. verwendet externe *.INI-Dateien (mit standardisiertem Aufbau), und auf dem ATARI sind die *.INF Dateien (chaotisch und überall anders) weit verbreitet. Dabei wäre es doch so einfach:
Variablen, die beim nächsten Programmstart automatisch mit dem alten Wert initialisiert sein sollen (in unserem Beispiel SaPrLongs[] & Sa-PrLens[7), müssen im DATA-Segment des Programms liegen. Für eine Hochsprache bedeutet dies, daß nur initialisierte globale Variablen dauerhaft gespeichert werden können. Lokale Variablen werden auf dem Stack abgelegt, ihr Inhalt ist nirgends in der Programmdatei abgelegt, und globale nicht initialisierte Variablen liegen im BSS-Segment (das es auf einem Datenträger gar nicht gibt, wird erst im Speicher aufgebaut).
Man braucht nun nur die Programmdatei zu öffnen, den Dateizeiger mit einem Seek an der richtigen Stelle zu positionieren und die Variable jetzt auszugeben. Die "richtige Stelle" ist gar nicht so schwer zu berechnen: Absolute Adresse der Variablen im Speicher minus Beginn des Textsegments plus Länge des Programm-Headers.
Zur eigenen Anwendung soll das Beispielprogramm SAVE_VAR.C studiert werden.
Bezüglich Überlistung oder Umgehung dieses Verfahrens ist folgendes zu sagen: Erstens: absolute Sicherheit gibt es prinzipiell in keinem datenverarbeitenden System (wer das Gegenteil behauptet, ist ein Bluffer). Zweitens: Diese Methode kann in mindestens zwei Fällen umgangen werden: Einem "Hacker", der von Hand diesen Schutzmechanismus außer Gefecht setzen will (etwa um einen Kopierschutz lahmzulegen oder Copyrights oder Seriennummern zu ändern), wird dies ein leichtes sein. Wer sich dagegen schützen will, sollte sich - nachdem er sich vor Augen geführt hat, daß ein Hacker mit genügender Verbissenheit und Zeitaufwand immer zum Ziel gelangen wird - schon eher mal mit Hardware-Dongles beschäftigen. Im zweiten Fall (das eigentliche Problem) läßt sich folgendes Szenario ausdenken: Ein Virus kennt den Code dieses Schutzmechanismus. Theoretisch kann er in einem Programm nun die Routine suchen, irgendwo die Adresse der Prüfsummen auslesen und diese nach seinen Veränderungen neu berechnen. Theoretisch deshalb, weil ein solcher Virus schon recht kompliziert wäre und damit groß würde (ein Problem für Viren, die möglichst klein und unauffällig bleiben müssen). Trotzdem sei es jedem Programmierer empfohlen, die Routine da und dort etwas umzubauen, damit möglichst viele verschiedene Versionen existieren.
... bleibt nur noch zu sagen, daß in Zukunft hoffentlich viele Software-Entwickler Ihre Programme ,virensicher(er)‘ machen werden (mit diesem oder anderen Verfahren). Denn Computerviren sind eine Gefahr - aber eine, die man mit geeigneten Mitteln gut bekämpfen kann.
Literatur:
[1] c’t, Ausgabe Juli ’92, S.146ff
[2] ATARI Profibuch ST-STE-TT, 10.Auflage 1991, Sybex-Verlag
/* PROJECT : COC - checksum on code (COC.C)
AUTHOR : Michael Vorburger
MODULE : main function
OUTPUT : <0 -> Virus, =0 -> OK,
>0 -> troubles!
Es werden Prüfsummen über das ganze Text-
segment berechnet und verglichen oder (beim
ersten Durchlauf) direkt ins Programm ge-
schrieben. Falls die gefundene Prüfsumme
identisch mit der gespeicherten war oder
zum ersten Mal berechnet wurde, liefert
die Funktion Null (0x0) als Rückgabewert.
Andere mögliche Werte (=Fehler!) sind:
-4 : Prüfsumme(n) stimmt nicht mehr. Virus!
-3 : Länge der Segmente oder gesamten Datei hat sich geändert!
-2 : Länge der Segmente in Basepage und Programmheader stimmen nicht überein!
0 : ALLES OK
1 : Die Programmdatei konnte nicht gefunden werden (Dateiname in SaPrProgName ?)
2 : Die Prüfsumme konnte (beim ersten Durchlauf) nicht geschrieben werden (wegen GEMDOS-Dateifehler). Schreibschutz oä.
KNOWN PROBLEMS:
relocation offset should never be 2 or 3
*/
#define HowManyBytesToTest MyBP->p_tlen
#include <portab.h>
#include <tos.h>
#include "coc.h"
extern char SaPrProgName[];
#define mod(x,y) (x % y)
#define MyBP _BasPag
ULONG SaPrLongs[2] = {0,0}; /* Globale */
ULONG SaPrLens[4] = {0,0,0,0}; /* Variablen! */
WORD SaPrSelfTest( void )
{
WORD ec; /* ExitCode */
PH PHeader; /* GEMDOS Programmkopf */
WORD FHandle; /* Dateihandle */
ULONG PrgLength; /* Lange der Programmdatei */
ULONG ChgdLong[2]; /* re-relozierte Longs */
ULONG *ReRelocAdr;
/* Zeiger auf Longword in ChdgLong */
UBYTE *PrgBegin = MyBP->p_tbase;
/* Zeiger auf Beginn TEXT-Segments */
UBYTE *PrgEnd = PrgBegin + HowManyBytesToTest;
/* Zeiger auf letztes zu prüfendes Byte */
#pragma warn -sus
ULONG *curr = PrgBegin;
#pragma warn +sus
/* Zeiger auf aktuelle Stelle */
ULONG FirstRelocAdr;/* erste Relozierungsadr */
UBYTE RelocOffset; /* Puffer beim Einlesen */
UBYTE *RelocAdr;
/* Zeiger auf zu (re)relozierende Adresse */
UBYTE *RelocRnd;
/* 'abgerundete' Relozierungsadr. */
ULONG CurLongs[2] = {0,0}; /* Prüfsummen */
if ( ( FHandle = Fopen( SaPrProgName, SaPrLens[0] == 0 ? FO_RW : FO_READ )) > -1 )
/* evt. wird auch geschrieben */
{
PrgLength = Fseek(0, FHandle, 2);
/* Dateilänge bestimmen, Position jetzt */
/* Fileende, darum wieder an den Anfang */
Fseek(0, FHandle, 0);
Fread(FHandle, sizeof(PH), &PHeader);
/* Header einlesen */
if ( ( PHeader.tlen == MyBP->p_tlen ) &
/* Wenn Header und Basepage */
( PHeader.dlen == MyBP->p_dlen ) &
/* nicht identisch, stimmt */
( PHeader.blen == MyBP->p_blen ) )
/* etwas nicht! */
{
if ( SaPrLens[0] == 0 )
/* beim ersten Duchlauf SaPrLens setzen */
{
SaPrLens[0] = PrgLength;
SaPrLens(1] = MyBP->p_tlen;
SaPrLens[2] = MyBP->p_dlen;
SaPrLens[3] = MyBP->p_blen;
Fseek( sizeof( PH ) + (ULONG)SaPrLens - (ULONG)PrgBegin, FHandle, 0);
Fwrite( FHandle, sizeof( SaPrLens ), SaPrLens );
/* Diese beiden Zeilen schreiben SaPrLens
direkt ins DATA-Segment der Programmdatei!
Beim nächsten Start sind sie damit initialisiert. */
}
if ( ( SaPrLens[0] == PrgLength ) &
/* vergleiche Dateilänge, */
( SaPrLens[1] == MyBP->p_tlen ) &
/* Länge des TEXT-, DATA- */
( SaPrLens[2] == MyBP->p_dlen ) &
/* und BSS-Segments mit */
( SaPrLens[3] == MyBP->p_blen ) )
/* urspr. Werten */
{
Fseek( sizeof( PH ) + PHeader.tlen + PHeader.dlen + PHeader.slen, FHandle, 0);
Fread(FHandle, 4, &FirstRelocAdr);
/* lies erste Relokationsadresse; Wenn */
/* es die nicht gibt? - Pech! */
RelocAdr = PrgBegin + FirstRelocAdr;
RelocRnd = (ULONG)RelocAdr - ( ( (ULONG)RelocAdr - (ULONG)PrgBegin ) % 4 ) ;
do
{
for ( ;
(ULONG)curr < (ULONG)RelocRnd;
curr++ )
{
CurLongs[0] ^= *curr;
CurLongs[1] += *curr;
/* Prüfsummen über unveränderte d.h.
nicht relozierte Bytes bilden */
}
ChgdLong[0] = *curr++;
ChgdLong[1] = *curr++;
/* lies die nächsten beiden Longs */
if ( FirstRelocAdr != 0L )
/* falls RelozierungsInformationen */
{
do {
#pragma warn -rpt
ReRelocAdr = (ULONG)ChgdLong + (ULONG)( RelocAdr - RelocRnd );
#pragma warn +rpt
/* Schnautze bei der Warnung <Non
portable pointer assignment>
Compiler meckert trotzdem... */
*ReRelocAdr -= (ULONG)PrgBegin;
CurLongs[0] ^= ChgdLong[0];
CurLongs[1] += ChgdLong[0];
do {
Fread(FHandle, 1, &RelocOffset);
if ( RelocOffset == 1 )
RelocAdr += 254L;
} while ( RelocOffset == 1 );
RelocAdr += RelocOffset;
RelocRnd = (ULONG)RelocAdr - (
( (ULONG)RelocAdr - (ULONG)PrgBegin ) % 4 );
/* nächste zu re-relozierende Adresse bestimmen */
if ( ( (ULONG)RelocAdr < (ULONG)curr )
& ( RelocAdr < PrgEnd )
& ( RelocOffset != 0 ) )
/* betr. Long schon im Puffer? */
{
ChgdLong[0] = ChgdLong[1];
ChgdLong[1] = *curr++;
/* wenn ja, nur weiterschieben */
}
else
{
CurLongs[0] ^= ChgdLong[1];
CurLongs[1] += ChgdLong[1];
/* nein, dann jetzt auch zweiten Teil verrechnen */
}
} while ( ( (ULONG)RelocAdr <
(ULONG)curr ) &
( RelocAdr < PrgEnd ) &
( RelocOffset != 0 ) );
}
} while ( ( (ULONG)RelocAdr < (ULONG)PrgEnd ) & ( RelocOffset != 0 ) );
for ( ;
(ULONG)curr < (ULONG)PrgEnd; curr+t )
{
CurLongs[0] ^= *curr;
CurLongs[1] += *curr;
}; /* evt. noch bis zum allerletzen Byte des TEXT-Segments machen */
if ( SaPrLongs[0] != 0 )
/* War das nicht der erste Durchlauf?
Ja, dann je nach dem, ob Prüfsumme
mit alter übereinstimmt, 0 oder -4
zurückliefern. */
ec = ( ( SaPrLongs[0]==CurLongs[0] ) &
( SaPrLongs[1]==CurLongs[1] ) ?
0 : -4 );
else
/* sonst (beim ersten Durchgang) Prüf-
summe direkt in die Datei schreiben */
{
Fseek( sizeof( PH ) + (ULONG)SaPrLongs - (ULONG)PrgBegin, FHandle, 0 );
ec = ( Fwrite( FHandle,
sizeof(CurLongs), CurLongs )
== sizeof(CurLongs) ? 0 : 2 );
}
}
else
ec=-3;
}
else
ec=-2;
Fclose(FHandle);
return ec;
}
else /* Problems while opening programfile */
return 1;
}
/* COC.H */
#include <portab.h>
WORD SaPrSelfTest( void );
typedef struct
{
WORD branch;
LONG tlen;
LONG dlen;
LONG blen;
LONG slen;
LONG res1;
LONG prgflags;
WORD absflag;
} PH;
/* DEMO.PRJ */
DEMO.TOS
=
PCSTART.O
DEMO.C
COC.C
PCTOSLIB.LIB
PCSTDLIB.LIB
/* PROJECT : COC (DEMO.C)
AUTHOR : Michael Vorburger
MODULE : demo program
EXPLANATION : This program shows the usage
of the virus defence COC.
*/
#include "coc.h"
#include <portab.h>
#include <tos.h>
char *mess[] =
{"\r\n COC - checksum on program /// "
"Testprogramm.\r\n",
"\x\n 1.Durchlauf: Prüfsumme wird "
"bestimmt und geschrieben.\r\n\n"};
const char SaPrProgName[] = "DEMO.TOS";
char *err[] = {" VIRUS! checksum has changed!",
" VIRUS! text-, data- or bss-segment's "
"or whole file's length has changed!",
" VIRUS! length of text-, data- or"
" bss-segment in basepage and"
"\n\r program-header are not equal!",
"???",
" OK - no virus.",
" PROBLEM: program-file not found! "
"should be <DEMO.TOS>.",
" PROBLEM: checksum not written to "
"file because of gemdos-problem" };
WORD main( void )
{
extern long SaPrLongs[2];
WORD i;
Cconws(mess[0]);
if ( ( SaPrLongs[0] & SaPrLongs[1] ) == 0 )
Cconws(mess[1]);
i=SaPrSelfTest();
Cconws(err[i+4]);
Cconws("\r\n");
Cconin();
return i;
}
/* PROGRAM : SAVE_VAR.C; demoprogram
AUTHOR : Michael Vorburger
EXPLANATION : Stores a global & initialized
variable into the text segment
of the executable's program
file so that it is initialized
with the old value at restart.
compile this with STANDARD.PRJ
*/
#include <stdio.h>
#include <tos.h>
unsigned int value = 18;
char string[255] = "Miky";
int main( void )
{
int FHandle;
printf("\n Last user's age: %u",value);
printf("\r\n Your age: ");
scanf("%d",&value);
printf("\n Last user's name: %s",string);
printf("\r\n Your age: ");
scanf ("%s", &string);
printf("\r\n Inputs are directly written "
"to program's data-segment in file.");
FHandle = Fopen( "SAVE_VAR.PRG", FO_RW );
if ( FHandle > -1 )
{
Fseek( 28 + (long)&value - (long)_BasPag->p_tbase, FHandle, 0 );
Fwrite( FHandle, sizeof(value), &value );
Fseek( 28 + (long)string - (long)_BasPag->p_tbase, FHandle, 0);
Fwrite( FHandle, sizeof(string), string );
Fclose( FHandle );
}
else
return FHandle;
return(0);
}