D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
201
Rozdział 6.
Podstawy programowania dla
hakerów
Język C
Dla kaŜdego hakera, młodego czy starego, mniej lub bardziej doświadczonego,
znajomość języka C jest jednym z fundamentów wiedzy. Niemal wszystkie narzędzia
i programy, stosowane w trakcie analiz sieci i włamań, powstają właśnie w tym
języku. RównieŜ w niniejszej ksiąŜce większość przedstawianego kodu to właśnie
kod źródłowy w języku C. Programy te moŜna modyfikować, dostosowywać do
własnych potrzeb i odpowiednio kompilować.
W pracy nad niniejszym rozdziałem wykorzystano obszerne fragmenty pracy guru
programowania Matthew Proberta. Mają one pełnić funkcję wprowadzenia do
programowania w języku C i umoŜliwić stosowanie przedstawianych w ksiąŜce (i
załączonych na CD-ROM-ie) listingów programów. Pełny kurs języka znajdziesz w
niejednej ksiąŜce wydawnictwa Helion.
Język C wyróŜniają następujące cechy, które omawiamy niŜej.
Blokowe konstrukcje sterowania wykonywaniem programu (typowe dla
większości języków wysokiego poziomu).
Swobodne operowanie podstawowymi obiektami „maszynowymi” (takimi jak
bajty) i moŜliwość odwoływania się do nich przy uŜyciu dowolnej, wymaganej
w danej sytuacji, perspektywy obiektowej (typowe dla języków asemblerowych).
MoŜliwość wykonywania operacji zarówno wysokiego poziomu (na przykład
arytmetyka zmiennoprzecinkowa), jak i niskiego poziomu (zbliŜonych
do instrukcji języka maszynowego), co umoŜliwia tworzenie kodu wysoce
zoptymalizowanego bez utraty jego przenośności.
202
Hack Wars. Tom 1. Na tropie hakerów
202
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Przedstawiony w niniejszym rozdziale opis języka C bazować będzie na funkcjach
oferowanych przez większość kompilatorów dla komputerów PC. Powinien dzięki
temu
umoŜliwić
rozpoczęcie
tworzenia
prostych
programów
osobom
nieposiadającym szerokiej wiedzy o języku (uwzględnimy między innymi funkcje
zapisane w pamięci ROM i funkcje DOS-u).
Przyjmujemy załoŜenie, Ŝe masz, drogi Czytelniku, dostęp do kompilatora C i
odpowiedniej dokumentacji funkcji bibliotecznych. Programy przykładowe powstały
w Turbo C firmy Borland; większość elementów niestandardowych tego narzędzia
uwzględniono równieŜ w późniejszych edycjach Microsoft C.
Wersje języka C
W pierwotnej edycji języka C (jeszcze przed publikacją Kernighana i Ritchie’ego,
The C Programming Language, Prentice-Hall 1988 (polskie wydanie: Język ANSI C,
Wydawnictwa Naukowo-Techniczne 1994)) zintegrowane operatory przypisania (+=,
*= itd.) definiowane były odwrotnie (tj. =+, =* itd.). Znakomicie utrudniało to
interpretację wyraŜeń takich jak:
x=-y
co mogłoby znaczyć
x = x - y
lub
x = (-y)
Ritchie szybko zauwaŜył dwuznaczność takiego zapisu i zmodyfikował go do postaci
znanej dzisiaj (+=, *= itd.). Mimo to wciąŜ stosowanych jest wiele odmian będących
rodzajem wypośrodkowania między pierwotną wersją języka C Kernighana i Ritchie’ego
a językiem ANSI C. RóŜnice między nimi dotyczą przede wszystkim:
wprowadzenia prototypów funkcji i zmiany preambuły definicji funkcji,
aby dostosować ją do stylu prototypów,
wprowadzenia znaku wielokropka (...) do oznaczenia list argumentów o zmiennej
długości,
wprowadzenia słowa kluczowego
void
(dla funkcji, które nie zwracają
wartości) i typu
void *
dla ogólnych zmiennych wskaźnikowych,
wprowadzenie w preprocesorze mechanizmów scalania ciągów, wklejania
elementu (token-pasting) i zamiany na ciąg (string-izing),
dodanie w preprocesorze translacji „trygrafów” (trigraph) — trójznakowych
sekwencji reprezentujących znaki specjalne,
dodanie w preprocesorze dyrektywy
#pragma
i formalizacja pseudofunkcji
declared()
,
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
203
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
203
wprowadzenie ciągów i znaków wielobajtowych, zapewniających obsługę
języków narodowych,
wprowadzenie słowa kluczowego
signed
(jako uzupełnienie słowa
unsigned
, stosowane w deklaracjach liczb całkowitych) i
jednoargumentowego operatora plus (
+
).
Klasyfikowanie języka C
Szerokie moŜliwości języka C, dopuszczenie bezpośredniego operowania na adresach
i danych w pamięci oraz strukturalne podejście do programowania sprawiają, Ŝe język
ten klasyfikuje się jako „język programowania średniego poziomu”. Znajduje to wyraz
w mniejszej liczbie gotowych rozwiązań niŜ w językach wysokiego poziomu, takich
jak BASIC, ale wyŜszym poziomie strukturalnym niŜ niskopoziomowy Assembler.
Słowa kluczowe
Pierwotna edycja języka C definiuje 27 słów kluczowych. Komitet ANSI dodał do
nich 5 nowych. Wynikiem są dwa standardy języka, choć norma ANSI przejęła
większość elementów od Kerninghana i Ritchie’ego. Oto lista:
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
float
short
unsigned
continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
Warto zwrócić uwagę, Ŝe niektóre kompilatory C wprowadzają dodatkowe słowa
kluczowe, specyficzne dla środowiska sprzętowego. Warto zapoznać się z nimi.
Struktura języka C
Język C wymaga programowania strukturalnego. Oznacza to, Ŝe na program składa
się pewna grupa nawzajem wywołujących się bloków kodu. Dostępne są róŜnorodne
polecenia słuŜące do konstruowania pętli i sprawdzania warunków:
do-while, for, while, if, case
Blok programu w języku C ujmowany jest w nawiasy klamrowe (
{}
). MoŜe on być
kompletną procedurą, nazywaną funkcją lub częścią kodu funkcji. Przyjrzyjmy się
przykładowi:
204
Hack Wars. Tom 1. Na tropie hakerów
204
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
if (x < 10)
{
a = 1;
b = 0;
}
Instrukcje wewnątrz nawiasów klamrowych wykonane zostaną tylko wtedy, gdy
spełniony zostanie warunek
x < 10
.
Jako kolejny przykład przedstawimy pełny blok kodu funkcji, zawierający wewnątrz
blok pętli:
int GET_X()
{
int x;
do
{
printf ("\nWprowadz liczbe z zakresu od 0 do 10 ");
scanf("%d",&x);
}
while(x < 0 || x > 10);
return(x);
}
Zwróćmy uwagę, Ŝe kaŜdy wiersz instrukcji zakończony jest średnikiem, o ile nie jest
sygnałem początku bloku kodu (w takim przypadku kolejnym znakiem jest nawias
klamrowy). Język C rozpoznaje wielkość liter, ale nie bierze pod uwagę białych
znaków. Odstępy między poleceniami są pomijane, stąd konieczność uŜycia średnika,
aby oznaczyć koniec wiersza. Tego rodzaju podejście powoduje, Ŝe następujące
polecenia interpretowane są jako identyczne:
x = 0;
x =0;
x=0;
Ogólna postać programu w języku C jest następująca:
instrukcje preprocesora kompilacji,
globalne deklaracje danych.
deklaracje i definicje funkcji (włączając w to zawartość programu):
typ-zwracany main (lista parametrów)
{
instrukcje
}
typ-zwracany f1 (lista parametrów)
{
instrukcje
}
typ-zwracany f2 (lista parametrów)
{
instrukcje
}
.
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
205
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
205
typ-zwracany fn (lista parametrów)
{
instrukcje
}
Komentarze
Podobnie jak większość języków, C pozwala umieszczać w kodzie programu
komentarze. Ich ogranicznikami są symbole
/*
i
*/
:
/* To jest wiersz komentarza w j
ę
zyku C */
(Równie często korzysta się z komentarzy jednoliniowych, otrzymywanych poprzez
sekwencję //, np.:
//To te
Ŝ
jest wiersz komentarza P.B.)
Biblioteki
Programy w języku C kompiluje się i łączy z funkcjami bibliotecznymi,
dostarczanymi wraz z kompilatorem. Na biblioteki składają się funkcje standardowe,
których działanie zdefiniowane zostało w normie ANSI. Ich powiązanie z
konkretnym kompilatorem zapewnia dostosowanie do platformy sprzętowej. Wynika
stąd, Ŝe standardowa funkcja biblioteczna
printf()
działa tak samo w systemach
DEC VAX i IBM PC, choć róŜni się jej, zapisany w bibliotece, kod maszynowy.
Programista C nie musi zagłębiać się w zawartość bibliotek, wymagana jest jedynie
umiejętność ich stosowania i znajomość działania funkcji, które pozostają niezmienne
na kaŜdym komputerze.
Tworzenie programów
Kompilacja
Zanim zajmiemy się funkcjami, poleceniami, sekwencjami i innymi zaawansowanymi
zagadnieniami, przyjrzyjmy się praktycznemu przykładowi, w którym doprowadzimy
do skompilowania kodu. Kompilowanie programów C jest stosunkowo prostą
czynnością, jednak róŜni się zaleŜnie od stosowanego kompilatora. Kompilatory
wyposaŜone w menu umoŜliwią skompilowanie, skonsolidowanie i uruchomienie
programu jednym wciśnięciem klawisza. Podchodząc jednak do zagadnienia
moŜliwie uniwersalnie i tradycyjnie, przeprowadzimy poniŜej całą procedurę w
oparciu o wiersz poleceń.
W dowolnym edytorze wprowadzamy poniŜszy fragment kodu i zapisujemy plik jako
przyklad.c:
/*
206
Hack Wars. Tom 1. Na tropie hakerów
206
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
przykładowy komunikat tekstowy
*/
#include<stdio.h>
void main()
{
printf( "Hello!\n" );
}
Kolejnym krokiem jest skompilowanie kodu do postaci pliku programu — dopiero
wtedy moŜna będzie go uruchomić (czy teŜ wykonać). W wierszu poleceń w tym
samym katalogu, w którym zapisaliśmy plik przyklad.c, wprowadzamy następujące
polecenie kompilacji:
cc przyklad.c
Nie wolno zapominać, Ŝe składnia polecenia kompilacji zaleŜy od kompilatora. Nasz
przykład opiera się na standardzie języka C. Współcześnie jednak popularne jest
stosowanie składni wywodzącej się z kompilatora GNU C:
gcc przyklad.c
Po wykonaniu takiego polecenia nasz kod jest juŜ skompilowany i ma postać pliku
programu, który moŜemy uruchomić. Wynik jego działania łatwo wydedukować
z prostego kodu:
Hello!
Press any key to continue
To wszystko! Kompilowanie małych programów w C nie jest trudne, naleŜy jedynie
mieć świadomość szkodliwych niekiedy efektów ich działania. Programy
przedstawiane na stronach tej ksiąŜki i załączone na CD-ROM-ie są oczywiście
znacznie bardziej skomplikowane, jednak zasady pozostają te same.
Typy danych
W języku C wyróŜnia się cztery podstawowe typy danych: znakowy, całkowity,
zmiennoprzecinkowy i nieokreślony. Odpowiadają im słowa kluczowe:
char
,
int
,
float
i
void
. Dalsze typy danych tworzy się na tej podstawie, dodając
modyfikatory:
signed
(ze znakiem),
unsigned
(bez znaku),
long
(długa) i
short
(krótka). Modyfikator
signed
jest elementem domyślnym, co sprawia, Ŝe jego uŜycie
moŜe się okazać konieczne jedynie w wypadku gdy zastosowano przełącznik
kompilacji nakazujący domyślne korzystanie ze zmiennych bez znaku. Rozmiar
kaŜdego typu danych zaleŜy od platformy sprzętowej, jednak norma ANSI wyznacza
pewne zakresy minimalne, zestawione w tabeli 6.1.
W praktyce tak określone konwencje oznaczają, Ŝe typ danych
char
nadaje się
najlepiej do przechowywania zmiennych typu znacznikowego, takich jako kody
stanu, o ograniczonym zakresie wartości. MoŜna równieŜ korzystać z typu
int
. Gdy
jednak zakres wartości nie przekracza 127 (lub 255 dla
unsigned char
), kaŜda
deklarowana w ten sposób zmienna przyczynia się do niepotrzebnego obciąŜania
pamięci.
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
207
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
207
Natomiast trudniejsze jest pytanie o to, z którego typu liczb rzeczywistych korzystać
—
float
,
double
czy
long double
. Gdy wymagana jest dokładność, na
przykład w aplikacji stosowanej w księgowości, instynktownie powinniśmy uŜyć typu
long
double
,
Tabela 6.1. Rozmiary i zakresy typów danych języka C
Typ
Rozmiar
Zakres
char
8
–128 do 127
unsigned char
8
0 do 255
int
16
–32 768 do 32 767
unsigned int
16
0 do 65 535
long int
32
–2 147 483 648 do 2 147 483 647
unsigned long int
32
0 do 4 294 967 295
float
32
precyzja 6-cyfrowa
double
64
precyzja 10-cyfrowa
long double
80
precyzja 10-cyfrowa
wiąŜe się to jednak z wykorzystaniem przez kaŜdą zmienną 10 bajtów. Obliczenia na
liczbach rzeczywistych nie są tak dokładne jak na liczbach całkowitych, warto więc
zawsze rozwaŜyć uŜycie typu
int
i „obejście” problemu. Typ danych
float
nie jest
zbyt dobry, gdyŜ jego 6-cyfrowa precyzja nie zapewnia dokładności, na której zawsze
będziemy mogli polegać. Ogólną zasadą jest korzystanie z typów całkowitych tak
szeroko, jak tylko jest to moŜliwe, a gdy pojawia się konieczność uŜycia liczb
rzeczywistych, wprowadzenie typu
double
.
Deklarowanie zmiennej
KaŜda zmienna musi zostać zadeklarowana przed uŜyciem. Ogólną postacią
deklaracji zmiennej jest:
typ nazwa;
Aby więc przykładowo zadeklarować zmienną
x
typu
int
, przeznaczoną do
przechowywania wartości z zakresu od –32 768 do 32 767, uŜyjemy instrukcji:
int x;
Ciągi znakowe deklarować moŜna jako tabele znaków:
char nazwa[liczba_elementów];
Deklaracja ciągu o nazwie
nazwisko
i długości 30 znaków, wyglądać będzie
następująco:
char nazwisko[30];
Tablice danych innych typów mogą mieć więcej niŜ jeden wymiar. Oto deklaracja
dwuwymiarowej tablicy liczb całkowitych:
208
Hack Wars. Tom 1. Na tropie hakerów
208
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
int x[10][10];
Elementy tablicy wywołujemy jako:
x[0][0]
x[0][1]
x[n][n]
WyróŜnia się trzy poziomy dostępu do zmiennych: lokalny, na poziomie modułu
i globalny. Zmienna deklarowana wewnątrz bloku kodu będzie dostępna wyłącznie
dla instrukcji wewnątrz tego bloku. Zmienna deklarowana poza blokami kodu funkcji,
ale poprzedzona modyfikatorem
static
, będzie dostępna wyłącznie instrukcjom
wewnątrz modułu kodu źródłowego. Zmienna deklarowana poza blokami kodu
funkcji i niepoprzedzona modyfikatorem będzie dostępna dla dowolnych instrukcji w
dowolnym module programu. Na przykład:
int blad;
static int a;
void main() (Co prawda funkcja main działa i bez deklaracji warto
ś
ci
zwracanej, jednak w takim przypadku wy
ś
wietla si
ę
ostrze
Ŝ
enie (bo
kompilator domy
ś
lnie przyjmuje j
ą
jako int i szuka funkcji return.
Aby tego unikn
ąć
, w ka
Ŝ
dym nast
ę
pnym przykładzie dopisuj
ę
void P.B.)
{
int x;
int y;
}
funkcjaa()
{
/* Sprawdzenie czy zmienna a jest równa 0 */
if (a == 0)
{
int b;
for(b = 0; b < 20; b++)
printf ("\nHello World");
}
}
W powyŜszym przykładzie zmienna
blad
jest dostępna dla wszystkich,
kompilowanych jako jeden program, modułów kodu źródłowego. Zmienna
a
jest
osiągalna dla wszystkich instrukcji w funkcjach
main()
i
funkcjaa()
, ale pozostaje
niewidoczna z poziomu innych modułów. Zmienne
x
i
y
są dostępne wyłącznie
instrukcjom wewnątrz funkcji
main()
. Z kolei zmienna
b
moŜe być uŜyta wyłącznie
przez instrukcje wewnątrz bloku kodu po instrukcji
if
.
JeŜeli drugi blok kodu faktycznie ma skorzystać ze zmiennej
blad
, wymagane będzie
umieszczenie w nim deklaracji zmiennej globalnej
extern
:
extern int blad;
funkcjab()
{
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
209
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
209
Język C nie stawia szczególnych przeszkód w przypisywaniu do siebie róŜnych typów
danych. Przykładowo moŜemy zadeklarować zmienną typu
char
, co spowoduje
przypisanie do przechowywania jej wartości jednego bajtu danych. MoŜna podjąć
próbę przypisania do niej wartości spoza tego zakresu:
void main()
{
x = 5000;
}
Zmienna
x
moŜe przechowywać wartości z zakresu od –127 do 128, a więc wartość
5000 nie zostanie przypisana.
x
przyjmie jednak wartość 136.
Potrzeba przypisania róŜnych typów danych nie jest niczym oryginalnym. Aby
powstrzymać kompilator od generowania ostrzeŜeń o takich operacjach, moŜna
skorzystać z instrukcji konwersji (cast statement), informując kompilator o tym, Ŝe
operacja wykonywana jest świadomie. Instrukcję taką budujemy, umieszczając przed
zmienną lub wyraŜeniem nazwę typu danych ujętą w nawiasy:
void main()
{
float x;
int y;
x = 100 / 25;
y = (int)x;
}
Operacja rzutowania
(int)
informuje kompilator o konieczności konwersji wartości
zmiennej zmiennoprzecinkowej
x
do liczby całkowitej, zanim ta zostanie przypisana
do zmiennej
y
.
Parametry formalne
Funkcja w języku C moŜe przyjmować parametry przekazywane przez funkcję
wywołującą. Parametry te deklaruje się podobnie jak zmienne, podając ich nazwy
wewnątrz towarzyszących nazwie funkcji nawiasów:
int MNOZ(int x, int y)
{
/* Zwró
ć
parametr x pomno
Ŝ
ony przez parametr y */
return(x * y);
}
void main()
{
int a;
int b;
int c;
a = 5;
b = 7;
c = MNOZ(a,b);
210
Hack Wars. Tom 1. Na tropie hakerów
210
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
printf("%d razy %d równa si
ę
%d\n",a,b,c);
}
Modyfikatory dostępu
Stosuje się dwa modyfikatory dostępu:
const
i
volatile
. Wartość zmiennej
zadeklarowanej jako
const
nie moŜe zostać zmieniona przez program, wartość
zmiennej zadeklarowanej jako
volatile
moŜe zostać zmieniona przez program.
Dodatkowo, zadeklarowanie zmiennej jako
volatile
uniemoŜliwia kompilatorowi
zaalokowanie jej do rejestru i ogranicza przeprowadzaną na niej optymalizację.
Typy klas przechowywania zmiennych
Język C przewiduje cztery rodzaje przechowywania zmiennych:
extern
,
static
,
auto
i
register
. Typ
extern
umoŜliwia modułowi kodu źródłowego dostęp do
zmiennej zadeklarowanej w innym module. Zmienne
static
dostępne są wyłącznie
z poziomu bloku kodu, w którym zostały zadeklarowane. Dodatkowo, jeŜeli zmienna
ma zasięg lokalny, zachowuje swoją wartość między kolejnymi wywołaniami bloku
kodu.
Zmienne rejestrowe (
register
) są, gdy tylko jest to moŜliwe, przechowywane w
rejestrach procesora. Zapewnia to najszybszy dostęp do ich wartości. Typ
auto
stosuje się wyłącznie w odniesieniu do zmiennych lokalnych. Nakazuje on
zachowywanie wartości zmiennej lokalnej. PoniewaŜ jest to modyfikator domyślny,
rzadko moŜna spotkać go w programach.
Operatory
Operatory to elementy kodu, które nakazują wykonanie obliczeń na zmiennych. W
języku C dostępne są następujące:
&
adres,
*
pośredniość,
+
plus jednoargumentowy,
-
minus jednoargumentowy,
~
dopełnienie bitowe,
!
negacja logiczna,
++
jako prefiks — preinkrementacja, jako sufiks — postinkrementacja,
––
jako prefiks — predekrementacja, jako sufiks — postdekrementacja,
+
dodawanie,
-
odejmowanie,
*
mnoŜenie,
/
dzielenie,
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
211
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
211
%
reszta z dzielenia (modulo),
<<
przesunięcie w lewo,
>>
przesunięcie w prawo,
&
bitowa operacja AND,
|
bitowa operacja OR,
^
bitowa operacja XOR,
&&
logiczna operacja AND,
||
logiczna operacja OR,
=
przypisanie,
*=
przypisanie iloczynu,
/=
przypisanie ilorazu,
%=
przypisanie reszty (modułu),
+=
przypisanie sumy,
-=
przypisanie róŜnicy,
<<=
przypisanie przesunięcia w lewo,
>>=
przypisanie przesunięcia w prawo,
&=
przypisanie wyniku bitowej operacji AND,
|=
przypisanie wyniku bitowej operacji OR,
^=
przypisanie wyniku bitowej operacji XOR,
<
mniejsze niŜ,
>
większe niŜ,
<=
mniejsze lub równe,
>=
większe lub równe,
==
równe,
!=
róŜne od,
.
bezpośredni selektor składnika,
->
pośredni selektor składnika,
a ? x :
y
jeŜeli
a
to prawda, to
x
, w przeciwnym razie
y,
[ ]
definiowanie tablic,
()
nawiasy oddzielają warunki i wyraŜenia,
...
wielokropek wykorzystuje się w listach parametrów formalnych prototypów
funkcji do deklarowania zmiennej liczby parametrów lub parametrów
zmiennych typów.
Aby zilustrować sposób korzystania z podstawowych operatorów, przyjrzyjmy się
krótkiemu programowi:
void main()
{
int a;
int b;
int c;
212
Hack Wars. Tom 1. Na tropie hakerów
212
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
a = 5; /*Przypisanie zmiennej a warto
ś
ci 5*/
b = a/2; /*Przypisanie zmiennej b warto
ś
ci a podzielonej przez 2*/
c = b*2; /*Przypisanie zmiennej c warto
ś
ci b pomno
Ŝ
onej przez 2*/
if (a == c) /*Sprawdzenie czy a ma tak
ą
sam
ą
warto
ść
jak c*/
puts("Zmienna a jest parzysta");
else
puts("Zmienna a jest nieparzysta");
}
Typowym sposobem zwiększenia wartości zmiennej o 1 jest wiersz:
x = x + 1
Język C dostarcza operatora inkrementacji, wystarczy więc napisać:
x++
W podobny sposób korzystamy z operatora dekrementacji, czyli zmniejszania
wartości o 1:
x--
Pozostałe operatory matematyczne wykorzystujemy podobnie. Warto jednak
pamiętać o wprowadzanych przez język C moŜliwościach zapisu skróconego:
Zapis typowy
Zapis w języku C
x = x + 1
x++
x = x - 1
x--
x = x * 2
x *= 2
x = x / y
x /= y
x = x % 5
x %= 5
Funkcje
Funkcje to procedury kodu źródłowego tworzące program w języku C. Ich ogólną
postacią jest:
zwracany_typ nazwa_funkcji(lista_parametrów)
{
instrukcje
}
Zwracany_typ
to typ zwracanej przez funkcję wartości:
char
,
int
,
double
,
void
itp. Kod wewnątrz funkcji C pozostaje niewidoczny dla innych funkcji C. Nie moŜna
wykonywać skoków z jednej funkcji do wnętrza innej. Funkcje mogą jedynie
wywoływać inne funkcje. Nie wolno równieŜ definiować funkcji wewnątrz innych
funkcji. Definicja musi zostać umieszczona bezpośrednio na poziomie modułu kodu.
Parametry przekazywane są do funkcji jako wartości lub jako odwołania (wskaźniki).
Gdy parametr jest przekazywany jako wartość, funkcja otrzymuje kopię tej wartości.
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
213
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
213
Parametr przekazywany jako odwołanie jest jedynie wskaźnikiem do właściwego
parametru. Pozwala to na zmianę jego wartości z poziomu wywołanej funkcji. W
poniŜszym przykładzie przekazujemy dwa parametry jako wartość do funkcji
funkcjaa()
, która następnie podejmuje próbę zmiany wartości przekazanych
zmiennych. Drugim krokiem jest przekazanie tych samych parametrów do funkcji
funkcjab()
, która równieŜ podejmuje próbę zmiany wartości zmiennych:
#include <stdio.h>
int funkcjaa(int x, int y)
{
/* Funkcja przyjmuje dwa parametry jako warto
ś
ci, x i y */
x = x * 2;
y = y * 2;
printf ("\nWarto
ść
x w funkcjaa() %d. Warto
ść
y w funkcjaa()
%d",x,y);
return(x);
}
int funkcjab(int *x, int *y)
{
/* Funkcja przyjmuje dwa parametry jako odwołania, x i y */
*x = *x * 2;
*y = *y * 2;
printf ("\nWarto
ść
x w funkcjab() %d. Warto
ść
y w funkcjab()
%d",*x,*y);
return(*x);
}
void main()
{
int x;
int y;
int z;
x = 5;
y = 7;
z = funkcjaa(x,y);
z = funkcjab(&x,&y);
printf ("\nWarto
ść
x %d, warto
ść
y %d, warto
ść
z %d",x,y,z);
}
funkcjab()
nie zmienia wartości otrzymanych parametrów. Modyfikowana jest
zawartość wskazywanych parametrami adresów pamięci. O ile
funkcjaa()
otrzymuje z funkcji
main()
wartości zmiennych
x
i
y
,
funkcjab()
otrzymuje z
funkcji
main()
ich adresy w pamięci.
214
Hack Wars. Tom 1. Na tropie hakerów
214
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Przekazywanie tablicy do funkcji
Następujący program przekazuje do funkcji tablicę, a funkcja nadaje wartości
elementom tablicy:
#include <stdio.h>
void funkcjaa(int x[])
{
int n;
for(n = 0; n < 100; n++)
x[n] = n;
}
void main()
{
int tablica[100];
int licznik;
funkcjaa(tablica);
for(licznik = 0; licznik < 100; licznik++)
printf ("\nWarto
ść
elementu %d wynosi %d",licznik,
tablica[licznik]);
}
Parametr funkcji,
int x[]
, jest tablicą dowolnej długości. Deklaracja taka jest
moŜliwa, poniewaŜ kompilator przekazuje jedynie adres początkowy tablicy, a nie
wartości poszczególnych jej elementów. Konsekwencją tego jest fakt, Ŝe funkcja
moŜe zmieniać wartości elementów tablicy. Aby uniemoŜliwić funkcji wprowadzanie
modyfikacji, konieczne jest uŜycie typu
const
:
funkcjaa(const int x[])
{
}
Przy takiej deklaracji wiersz zmieniający zawartość tablicy wywołałby błąd
kompilacji. Określenie parametru jako wartości stałej nie likwiduje jednak
pośredniości jego przekazania. Ilustruje to poniŜszy program:
#include <stdio.h>
void funkcjaa(const int x[])
{
int *ptr;
int n;
/*Ten wiersz generuje ostrze
Ŝ
enie 'suspicious pointer conversion'*/
/*(niebezpieczna konwersja wska
ź
nika)*/
/*x jest wska
ź
nikiem const, a ptr - nie*/
ptr = x;
for(n = 0; n < 100; n++)
{
*ptr = n;
ptr++;
}
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
215
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
215
void main()
{
int tablica[100];
int licznik;
funkcjaa(tablica);
for(licznik = 0; licznik < 100; licznik++)
printf("\nWarto
ść
elementu %d wynosi
%d",licznik,tablica[licznik]);
}
Przekazywanie parametrów funkcji main()
Język C umoŜliwia przekazanie parametrów do uruchamianego programu z poziomu
systemu operacyjnego. Do ich odczytania wykorzystuje się zmienne
argc
i
argv[]
:
#include <stdio.h>
void main(int argc, char *argv[])
{
int n;
for(n = 0; n < argc; n++)
printf ("\nWartosc parametru %d to %s",n,argv[n]);
}
Parametr
argc
przechowuje liczbę przekazanych programowi parametrów. W tablicy
argv[]
zapisane są ich adresy;
argv[0]
jest zawsze nazwą uruchamianego
programu. Mechanizm ten ma szczególne znaczenie dla aplikacji wymagających
dostępu do plików systemowych i danych. RozwaŜmy następującą sytuację: mała
aplikacja obsługi baz danych przechowuje swoje dane w pojedynczym pliku dane.dat;
aplikacja ta musi zostać tak zaprojektowana, aby moŜna było uruchomić ją z
dowolnego katalogu, czy to na dysku twardym, czy dyskietce; musi równieŜ zapewnić
uruchamianie za pośrednictwem ścieŜki wyszukiwania DOS-u (
path
). Do poprawnej
pracy aplikacji jest więc wymagane, aby zawsze mogła odnaleźć plik dane.dat.
Rozwiązanie takie zapewni przyjęcie załoŜenia, Ŝe plik danych jest zawsze w
identycznym katalogu co sam program. PoniŜszy fragment ilustruje wykorzystanie
parametrów
argc
i
argv
w celu utworzenia ścieŜki do pliku danych aplikacji:
#include <string.h>
char nazwa_pliku[160];
void main(int argc, char *argv[])
{
char *plik_danych = "DATA.DAT";
char *p;
strcpy(nazwa_pliku,argv[0]);
p = strstr(nazwa_pliku, ".exe"); (kompilator tworzy plik z
rozszerzeniem o małych literach P.B.)
if (p == NULL)
{
/* Plik uruchomieniowy jest plikiem .COM */
216
Hack Wars. Tom 1. Na tropie hakerów
216
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
p = strstr(nazwa_pliku, ".com");
}
/* Wyszukujemy ostatni uko
ś
nik */
while(*(p-1) != '\\')
p--;
strcpy(p,plik_danych);
}
Przedstawiony program tworzy i zapisuje w zmiennej
nazwa_pliku
ciąg postaci
ś
cieŜka\dane.dat. JeŜeli więc przykładową nazwą pliku uruchomieniowego będzie
test.exe i zostanie on umieszczony w katalogu \borlandc, zmiennej
nazwa_pliku
przypisany zostanie ciąg
\borlandc\dane.dat
.
Wyjście z funkcji
Polecenie
return
powoduje natychmiastowe wyjście z funkcji. JeŜeli w deklaracji
funkcji podano typ zwracanej wartości, w poleceniu
return
naleŜy uŜyć parametru
tego samego typu.
Prototypy funkcji
Prototypy funkcji umoŜliwiają kompilatorowi C sprawdzanie poprawności
przekazywanych, do i z funkcji, danych. Ma to istotne znaczenie jako zabezpieczenie
przed przekroczeniem zakresu zaalokowanego dla zmiennej obszaru pamięci. Prototyp
funkcji umieszcza się na początku programu po poleceniach preprocesora (takich jak
#include
) i przed deklaracjami funkcji.
Polecenia preprocesora C
W języku C w treści kodu źródłowego moŜna umieszczać polecenia dla kompilatora.
Określa się je terminem polecenia preprocesora. Norma ANSI definiuje następujące:
#if
#ifdef
#ifndef
#else
#elif
#endif
#include
#define
#undef
#line
#error
#pragma
Wszystkie polecenia preprocesora rozpoczyna znak krzyŜyka (hash), czyli #. KaŜde
wymaga osobnego wiersza kodu (uzupełnionego ewentualnie komentarzem). PoniŜej
przedstawiamy krótkie omówienie.
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
217
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
217
#define
Polecenie
#define
tworzy identyfikator, który kompilator zastąpi podanym ciągiem
w danym module kodu źródłowego. Na przykład:
#define FALSE 0
#define TRUE !FALSE
Kompilator zastąpi wszystkie dalsze wystąpienia ciągu
FALSE
znakiem
0
, a wszystkie
dalsze wystąpienia ciągu
TRUE
— ciągiem
!0
. Zastępowaniu nie podlegają
identyfikatory wewnątrz znaków cudzysłowu, a więc wiersz:
printf ("TRUE");
nie zostanie zmieniony, ale
printf ("%d",FALSE);
podlega modyfikacji.
Polecenie
#define
moŜe równieŜ zostać uŜyte do definiowania makr, takŜe makr z
parametrami. Do zapewnienia poprawności zastąpień zaleca się ujmowanie
parametrów w nawiasy. W poniŜszym przykładzie deklarujemy makro o nazwie
larger()
, przyjmujące dwa parametry i zwracające ten z nich, którego wartość jest
większa.
#include <stdio.h>
#define larger(a,b) (a > b) ? (a) : (b)
int main()
{
printf("\n%d jest wi
ę
ksze",larger(5,7));
}
#error
Polecenie
#error
powoduje przerwanie procesu kompilacji i wyświetlenie podanego
tekstu, na przykład:
#error SKOMPILOWANE DO MODULU B
powoduje zatrzymanie kompilacji i wyświetlenie:
SKOMPILOWANE DO MODULU B
#include
Polecenie
#include
nakazuje kompilatorowi odczytanie i przetworzenie zawartości
dodatkowego pliku źródłowego. Nazwa pliku musi zostać ujęta w cudzysłów lub
wstawiona między znaki
<>
, na przykład:
#include "module2.c"
#include <stdio.h>
218
Hack Wars. Tom 1. Na tropie hakerów
218
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
JeŜeli nazwa pliku została wpisana między znaki
<>
, kompilator wyszukuje go w
katalogu określonym w konfiguracji. Jest to zasada ogólna.
#if, #else, #elif, #endif
Grupa poleceń
#if
dostarcza mechanizmu kompilacji warunkowej. Stosowana jest
dość typowa składnia:
#if wyra
Ŝ
enie_stale
instrukcje
#else
instrukcje
#endif
Polecenie
#elif
to skrócona postać
#else if
:
#if wyra
Ŝ
enie
instrukcje
#elif wyra
Ŝ
enie
instrukcje
#endif
#ifdef, #ifndef
Rozwinięciem tych poleceń jest
#if defined
(jeŜeli zdefiniowano) i
#if not
defined
(jeŜeli nie zdefiniowano). Konstrukcje składniowe są następujące:
#ifdef nazwa_makra
instrukcje
#else
instrukcje
#endif
#ifndef nazwa_makra
instrukcje
#else
instrukcje
#endif
nazwa_makra
to identyfikator utworzony za pomocą deklaracji
#define
.
#undef
Polecenie
#undef
usuwa definicję makra utworzonego przy uŜyciu wcześniejszej
instrukcji
#define
.
#line
Polecenie
#line
modyfikuje zmienne globalne kompilatora
__LINE__
i
__FILE__
.
Ogólną postacią instrukcji jest:
#line numer "nazwa_pliku"
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
219
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
219
Wartość
numer
zostaje umieszczona w zmiennej
__LINE__
, a
"nazwa_pliku"
— w
zmiennej
__FILE__
.
#pragma
UmoŜliwia korzystanie z poleceń specyficznych dla kompilatora.
Instrukcje sterujące
Jak w kaŜdym języku programowania, równieŜ w C, znajdziemy instrukcje
sprawdzające wartość wyraŜenia. Wynikiem takiego sprawdzenia jest wartość
TRUE
lub
FALSE
. Wartości
FALSE
odpowiada liczba
0
, a
TRUE
— liczba róŜna od zera.
Instrukcje wykonania warunkowego
Podstawową instrukcją wykonania warunkowego jest
if
o następującej składni:
if (wyra
Ŝ
enie)
instrukcje
else
instrukcje
gdzie
instrukcje
moŜe być instrukcją pojedynczą lub ujętym w nawiasy klamrowe
blokiem kodu. Element
else
jest opcjonalny. JeŜeli wartością
wyra
Ŝ
enie
jest
TRUE
,
wykonywana jest instrukcja podana bezpośrednio po nim. W pozostałych przypadkach
wykonywana jest instrukcja podana po słowie
else
(o ile ta część składni została
uŜyta).
Alternatywą dla konstrukcji
if...else
jest polecenie
?:
w postaci:
wyra
Ŝ
enie ? instrukcja_prawda : instrukcja_fałsz
JeŜeli wartością wyraŜenia jest
TRUE
, wykonywana jest pierwsza instrukcja. W
pozostałych przypadkach wykonywana jest instrukcja druga. Ilustruje to przykład:
#include <stdio.h>
void main()
{
int x;
x = 6;
printf("\nx to liczba %s", x % 2 == 0 ? "parzysta" :
"nieparzysta");
}
Język C oferuje równieŜ instrukcję
switch
, ułatwiającą porównywanie wyraŜenia
z pewną listą wartości. Wykonywane są instrukcje powiązane z pierwszą dopasowaną
wartością listy. Składnia polecenia
switch
jest następująca:
switch (wyra
Ŝ
enie)
{
case warto
ść
1 : instrukcje
220
Hack Wars. Tom 1. Na tropie hakerów
220
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
break;
case warto
ść
2 : instrukcje
break;
.
.
case warto
ść
n : instrukcje
break;
default : instrukcje
}
UŜycie instrukcji
break
nie jest wymagane, ale jej pominięcie powoduje dalsze
porównywanie wyraŜenia z kolejnymi elementami listy wartości.
#include <stdio.h>
void main()
{
int x;
x = 6;
switch (x)
{
case 0 : printf ("\nx równa si
ę
zero");
break;
case 1 : printf ("\nx równa si
ę
jeden");
break;
case 2 : printf ("\nx równa si
ę
dwa");
break;
case 3 : printf ("\nx równa si
ę
trzy");
break;
default : printf ("\nx jest wi
ę
ksze od trzech");
}
}
Instrukcje
switch
moŜna zagnieŜdŜać.
Instrukcje iteracji
W języku C stosuje się trzy instrukcje pętli (iteracji):
for
,
while
i
do-while
.
Składnia pętli
for
jest następująca:
for(inicjalizacja;warunek;inkrement)
instrukcje
Jest ona szczególnie przydatna, gdy korzystamy z licznika, jak w poniŜszym
przykładzie wyświetlającym zestaw znaków ASCII:
#include <stdio.h>
void main()
{
int x;
for(x = 32; x < 128; x++)
printf ("%d\t%c\t",x,x);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
221
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
221
}
Dopuszczalna jest równieŜ nieskończona pętla
for
:
for(;;)
{
instrukcje
}
Język C pozwala uŜywać teŜ pustych instrukcji. PoniŜsza pętla usuwa z ciągu
początkowe znaki odstępu:
for(; *str == ' '; str++)
;
Warto zwrócić uwagę na średniki odpowiadające inicjalizacji pętli i pustej instrukcji.
Pętla
while
ma konstrukcję nieco prostszą:
while(warunek)
instrukcje
Instrukcja lub blok instrukcji (ujęty w nawiasy klamrowe) będą powtarzane do czasu,
gdy wyraŜenie warunku przyjmie wartość
FALSE
. JeŜeli wyraŜenie nie jest
prawdziwe jeszcze przed wejściem do pętli, instrukcje nie będą wykonywane w
ogóle. Jest to istotna róŜnica w stosunku do pętli
do-while
, która zawsze zostaje
wykonana co najmniej raz. Jej składnia to:
do
{
instrukcje
}
while(warunek);
Instrukcje skoku
Instrukcja
return
pozwala powrócić z funkcji wykonywanej do funkcji, z której ta
została wywołana. W zaleŜności od zadeklarowanego typu wartości zwracanej przez
funkcję instrukcja
return
moŜe wymagać odpowiedniego parametru:
int MULT(int x, int y)
{
return(x * y);
}
lub
void funkcjaa()
{
printf ("\nHello World");
return; (w tym wypadku return nie jest konieczny P.B.)
}
Instrukcja
break
słuŜy do wychodzenia z pętli lub instrukcji
switch
. W przypadku
pętli powoduje to jej przedwczesne zakończenie, jak w poniŜszym przykładzie:
#include <stdio.h>
222
Hack Wars. Tom 1. Na tropie hakerów
222
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
void main()
{
int x;
for(x = 0; x < 256; x++)
{
if (x == 100)
break;
printf ("%d\t",x);
}
}
Uzupełnieniem
break
jest polecenie
continue
, wymuszające przeprowadzenie
następnej iteracji pętli. Kolejną wykonywaną instrukcją jest w tym przypadku
instrukcja pętli (dalsze instrukcje w iterowanym bloku są pomijane). Dostępna jest
równieŜ funkcja przedwczesnego zakończenia wykonywania programu —
exit()
.
MoŜna za jej pomocą przekazać wartość zwracaną do programu wywołującego:
exit(warto
ść
_zwracana);
Continue
Słowo kluczowe
continue
nakazuje skok do instrukcji kontrolnej pętli. W
przypadku pętli zagnieŜdŜonych jest to instrukcja pętli wewnętrznej (
while
,
do...while()
). To sposób na łagodne zakończenie pętli jak w poniŜszym
przykładzie, gdzie odczytujemy zapisane w pliku ciągi:
#include <stdio.h>
void main()
{
FILE *fp;
char *p;
char buff[100];
fp = fopen("dane.txt","r");
if (fp == NULL)
{
fprintf(stderr,"Nie mo
Ŝ
na otworzy
ć
pliku data.txt");
exit(0);
}
do
{
p = fgets(buff,100,fp);
if (p == NULL)
/* Wymuszenie wyj
ś
cia */
continue;
puts(p);
}
while(p);
}
W przypadku pętli
for
instrukcja
continue
powoduje najpierw wykonanie
wyraŜenia inkrementacji, a dopiero po nim następuje sprawdzenie warunku
zakończenia.
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
223
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
223
Wejście-wyjście
Pobieranie danych
Program w języku C moŜe pobierać dane z konsoli (która jest standardowym
urządzeniem wejściowym), pliku lub portu. Ogólnym poleceniem odczytu danych ze
standardowego strumienia wejściowego
stdin
jest
scanf()
. Skanuje ono po jednym
znaku kolejne pola wejściowe. Podlegają one formatowaniu zgodnie z pierwszym z
przekazanych funkcji
scanf()
parametrów. Następnie pole zostaje zapisane pod
adresem przekazanym jako kolejny parametr wywołania funkcji. Przykładowy
program odczytuje pojedynczą liczbę całkowitą ze strumienia
stdin
:
void main()
{
int x;
scanf("%d", &x);
}
Warto zwrócić uwagę na operator uŜyty jako prefiks zmiennej
x
na liście parametrów
wywołania funkcji
scanf()
. Funkcja ta zapisuje bowiem wartość pod określonym
adresem, nie posługując się mechanizmem przypisywania wartości zmiennej.
Ciągiem formatującym jest ciąg znakowy, który moŜe zawierać trzy typy danych: znaki
odstępu (spacja, tabulator, przejście do nowego wiersza), znaki właściwe (wszystkie
znaki ASCII z wyjątkiem znaku %) i specyfikatory formatowania. Specyfikatory te
mają następującą składnię:
%[*][szeroko
ść
][h|l|L]typ
Oto przykład:
#include <stdio.h>
void main()
{
char nazwisko[30];
int wiek;
printf ("Podaj nazwisko i wiek ");
scanf("%30s%d",nazwisko,&wiek);
printf ("\n%s %d",nazwisko,wiek);
}
Zwróćmy uwagę na wiersz
#include <stdio.h>
— nakazuje on kompilatorowi
przetwarzanie pliku nagłówkowego stdio.h, w którym zawarte są prototypy funkcji
scanf()
i
printf()
. Po uruchomieniu tego prostego programu łatwo przekonamy
się, Ŝe uŜycie znaku odstępu przerwie wprowadzanie pierwszego pola danych.
Alternatywną funkcją pobierania danych jest
gets()
, odczytująca ciąg znaków ze
strumienia
stdin
do momentu napotkania znaku nowego wiersza. W ciągu docelowym
znak nowego wiersza zastąpiony zostaje znakiem
NULL (0)
. Charakterystyczna dla tej
funkcji jest moŜliwość odczytywania znaków odstępu. Oto nowa wersja powyŜszego
programu (korzystająca z
gets()
w miejsce
scanf()
):
224
Hack Wars. Tom 1. Na tropie hakerów
224
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
char dane[80];
char *p;
char nazwisko[30];
int wiek;
printf ("\nPodaj nazwisko i wiek ");
/* Odczyt ci
ą
gu danych */
gets(dane);
/* p jest wska
ź
nikiem do ostatniego znaku pobieranego ci
ą
gu */
p = &dane[strlen(dane) - 1];
/* Usuwamy spacje ko
ń
cowe, zast
ę
puj
ą
c je znakami NULL */
while(*p == ' '){
*p = 0;
p--;
}
/* Lokalizujemy ostatni
ą
spacj
ę
w ci
ą
gu */
p = strrchr(dane,' ');
/* Odczytujemy wiek i zamieniamy na liczb
ę
*/
wiek = atoi(p);
/* Wstawiamy znak ko
ń
ca ci
ą
gu przed polem wieku */
*p = 0;
/* Kopiujemy ci
ą
g danych do zmiennej */
strcpy(nazwisko, dane);
/* Wy
ś
wietlamy wyniki operacji */
printf ("\nNazwisko: %s, wiek: %d", nazwisko, wiek);
}
Wyprowadzanie danych
Podstawową funkcją wyprowadzania danych jest
printf()
. Jest ona podobna do
scanf()
z tą róŜnicą, Ŝe zapisuje dane do standardowego strumienia wyjściowego
stdout
. Funkcja pobiera listę pól danych wyjściowych, odpowiednio stosuje
specyfikatory formatowania i wyprowadza wynik. MoŜna stosować takie same
przekształcenia formatujące jak w przypadku funkcji
scanf()
, jak równieŜ
dodatkowe znaczniki:
-
wyrównuje dane wyjściowe do lewej, uzupełniając je z prawej strony
znakami odstępu międzywyrazowego (spacji),
+
wymusza poprzedzanie liczb znakiem.
Nieco odmienna jest takŜe postać specyfikatora szerokości. Jest on rozbudowany o
element określający precyzję:
szeroko
ść
.precyzja
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
225
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
225
Aby więc wyświetlić liczbę zmiennoprzecinkową z dokładnością do trzech miejsc
dziesiętnych, piszemy:
printf ("%.3f",x);
PoniŜej przedstawiamy listę specjalnych stałych znakowych, które mogą pojawić się
na liście parametrów funkcji
printf()
:
\n
nowy wiersz (NL),
\r
powrót karetki (CR),
\t
tabulator,
\b
znak cofania (backspace),
\f
znak nowej strony,
\v
tabulator pionowy,
\\
ukośnik odwrotny (backslash),
\'
apostrof,
\"
cudzysłów,
\?
znak zapytania,
\o
ciąg w notacji ósemkowej,
\x
ciąg w notacji szesnastkowej.
Kolejny program ilustruje, w jaki sposób wyświetlić liczbę całkowitą w postaci
dziesiętnej, szesnastkowej i ósemkowej. Liczba
04
po znaku procentów (
%
) w
instrukcji
printf()
nakazuje kompilatorowi dopełnienie wyświetlanej liczby do
szerokości co najmniej czterech cyfr:
/* Prosty program konwersji liczb dziesi
ę
tnych */
/* do postaci szesnastkowej i ósemkowej */
#include <stdio.h>
void main()
{
int x;
do
{
printf ("\nPodaj liczb
ę
(lub 0, aby zako
ń
czy
ć
) ");
scanf("%d",&x);
printf ("%04d %04X %04o",x,x,x);
}
while (x != 0);
}
Do funkcji pokrewnych
printf()
naleŜy
fprintf()
, której prototyp ma postać:
fprintf(FILE *fp, char *format[,argument,...]);
Jej zadaniem jest przesyłanie sformatowanych danych wyjściowych do określonego
strumienia plikowego.
226
Hack Wars. Tom 1. Na tropie hakerów
226
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Kolejną tego rodzaju funkcją jest
sprintf()
o prototypie:
sprintf(char *s, char *format[,argument,...]);
Alternatywą dla
printf()
jest
puts()
, funkcja przesyłająca prosty ciąg do
strumienia
stdout
. Przesyłany ciąg zostaje automatycznie uzupełniony znakiem
nowego wiersza. Jest to rozwiązanie szybsze od
printf()
, jednak jego moŜliwości
są ograniczone.
Bezpośrednia wymiana danych z konsolą
Do przesyłania i odczytu danych z konsoli (klawiatury i ekranu) moŜna
wykorzystywać równieŜ bezpośrednie funkcje we-wy. WyróŜnia je litera „c” na
początku — odpowiednikiem
printf()
jest więc
cprintf()
, a odpowiednikiem
puts()
— funkcja
cputs()
. RóŜnice między funkcjami bezpośredniej wymiany
danych a funkcjami standardowymi są następujące.
Nie są wykorzystywane strumienie predefiniowane, nie moŜna więc przekierować
danych przesyłanych funkcjami komunikacji bezpośredniej.
Funkcji bezpośrednich nie moŜna przenosić między róŜnymi systemami
operacyjnymi (m.in. nie moŜna z nich korzystać w programach dla Windows).
Funkcje bezpośrednie są szybsze niŜ standardowe.
Nie zapewniają współpracy ze wszystkimi trybami wyświetlania (zwłaszcza
trybami graficznymi VESA).
Wskaźniki
Wskaźnik to zmienna, która przechowuje adres elementu danych w pamięci.
Deklaracja wskaźnika jest podobna do deklaracji zwykłej zmiennej, ale nazwa
poprzedzana jest znakiem gwiazdki (
*
), na przykład:
char *p;
PowyŜszy wiersz deklaruje zmienną
p
jako wskaźnik do zmiennej typu
char
.
Wykorzystanie wskaźników dostarcza szerokich moŜliwości, wymaga jednak
szczególnej
uwagi.
Skutki
przypisania
błędnego
adresu
są
najczęściej
nieprzewidywalne. Oto przykład prostego programu, w którym wykorzystywany jest
wskaźnik:
#include <stdio.h>
void main()
{
int a;
int *x;
/* x jest wska
ź
nikiem do danych typu int */
a = 100;
x = &a;
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
227
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
227
printf("\nZmienna a przechowuje warto
ść
%d pod adresem %p.",a,x);
}
Wartości wskaźników moŜna zwiększać i zmniejszać, dopuszczalne są równieŜ inne
operacje matematyczne. Typowym zastosowaniem wskaźników jest zapewnienie
dynamicznego przydziału pamięci. W trakcie pracy programu często pojawia się
potrzeba przejściowego (tymczasowego) zaalokowania bloku pamięci. Korzystamy
wówczas z funkcji
malloc()
:
wska
ź
nik_dowolnego_typu = malloc(liczba_bajtów);
Funkcja
malloc()
zwraca wskaźnik typu
void
, co oznacza, Ŝe moŜe on wskazywać
dane dowolnego typu —
int
,
char
,
float
itd. W poniŜszym przykładzie alokujemy
pamięć dla tabeli 1000 liczb całkowitych.
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *x;
int n;
/* x jest wska
ź
nikiem do danych typu int */
/* Tworzymy tablic
ę
1000-elementow
ą
*/
/* sizeof() dostarcza kompilatorowi informacji o liczbie */
/* bajtów wymaganej do przechowywania zmiennej typu int */
x = malloc(1000 * sizeof(int));
/* Sprawdzamy czy alokacja została wykonana */
if (x == NULL)
{
printf("\nNie mo
Ŝ
na zaalokowa
ć
pami
ę
ci dla 1000-elementowej
tablicy warto
ś
ci
int");
exit(0);
}
/* Przypisujemy warto
ś
ci poszczególnym elementom */
for(n = 0; n < 1000; n++)
{
*x = n;
x++;
}
/* Przywracamy x warto
ść
adresu pocz
ą
tkowego tabeli */
x -= 1000;
/* Wy
ś
wietlamy warto
ś
ci tabeli */
for(n = 0; n < 1000; n++){
printf("\nElement %d przechowuje warto
ść
%d",n,*x);
x++;
}
/* Po u
Ŝ
yciu, dealokujemy blok pami
ę
ci */
free(x);
}
228
Hack Wars. Tom 1. Na tropie hakerów
228
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Wskaźniki wykorzystuje się równieŜ w odniesieniu do tablic znaków, czyli ciągów
(strings). PoniewaŜ wszystkie ciągi w programach C kończy bajt o wartości 0,
korzystając ze wskaźnika, moŜemy policzyć znaki w ciągu:
#include <stdio.h>
#include <string.h>
void main()
{
char *p;
char tekst[100];
int dlugosc;
/* Inicjujemy zmienn
ą
'tekst' */
strcpy(tekst,"To jest ciag znakowy");
/* Ustawiamy warto
ść
zmiennej p na pocz
ą
tek tekstu */
p = tekst;
/* Inicjujemy zmienn
ą
długo
ść
*/
dlugosc = 0;
/* Zliczamy znaki w zmiennej tekst */
while(*p)
{
dlugosc++;
p++;
}
/* Wy
ś
wietlamy wynik */
printf("\nDlugosc ciagu znakowego to: %d",dlugosc);
}
Wymaganą do zaadresowania 1 MB pamięci 20-bitową liczbę dzieli się na dwie
wartości: przesunięcie (offset) i segment (kaŜdy segment to 64 kB). Do przechowywania
numerów segmentów pamięci komputer IBM PC wykorzystuje tzw. rejestry
segmentowe. Konsekwencją takiego rozwiązania są w języku C trzy dodatkowe słowa
kluczowe:
near
— wskaźniki „bliskie” mają rozmiar 16 bitów i umoŜliwiają dostęp
do danych bieŜącego segmentu,
far
— wskaźniki „dalekie” obejmują wartości określające przesunięcie
i segment, umoŜliwiając dostęp do dowolnego adresu w pamięci,
huge
— wskaźniki „ogromne” to odmiana wskaźników dalekich, zapewniająca
moŜliwość zwiększania i zmniejszania wartości w całym zakresie 1 MB
(kompilator generuje odpowiedni kod modyfikujący wartość przesunięcia).
Nie będzie zapewne zaskakujące stwierdzenie, Ŝe przetwarzanie programu
korzystającego ze wskaźników typu
near
będzie szybsze niŜ w przypadku programu, w
którym zastosowano wskaźniki
far
. Wskaźniki
huge
są oczywiście największym
obciąŜeniem. Kompilatory C wyposaŜone są w makro zwracające adres
odpowiadający podanym wartościom numeru segmentu i przesunięcia:
void far *MK_FP(unsigned segment, unsigned offset);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
229
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
229
Struktury
Język C oferuje technikę grupowania zmiennych pod jedną nazwą, dostarczając w ten
sposób wygodnego sposobu przechowywania powiązanych ze sobą informacji i
strukturalizowania ich. Składnia definicji struktury jest następująca:
typedef struct
{
typ_zmiennej nazwa_zmiennej;
typ_zmiennej nazwa_zmiennej;
.
.
.
}
nazwa_struktury;
UŜywanie zmiennych strukturalnych jest niezbędne przy korzystaniu z plików, w
których występuje uporządkowanie oparte na rekordach danych. W poniŜszym
przykładzie operować będziemy na prostym pliku z listą adresów. Rozpoczniemy od
deklaracji struktury
dane
, złoŜonej z sześciu pól:
nazwisko
,
adres
,
miasto
,
wojewodztwo
,
poczta
i
nrtelefonu
:
typedef struct
{
char nazwisko[30];
char adres[30];
char miasto[30];
char wojewodztwo[30];
char kod[6];
char nrtelefonu[15];
}
dane;
Odwołania do pól zmiennej strukturalnej mają postać:
zmienna_strukt.nazwa_pola;
Nie ma ograniczenia liczby pól struktury, nie jest równieŜ wymagane, aby typy pól
były takie same lub podobne, na przyklad:
typedef struct
{
char nazwisko[30];
int wiek;
char *notatki;
}
dp;
Jest to poprawna deklaracja struktury obejmująca: pole tablicy znakowej, pole liczby
całkowitej i pole wskaźnika do zmiennej znakowej. Aby przekazać zmienną
strukturalną jako parametr, korzystamy z jej adresu — poprzedzamy nazwę zmiennej
operatorem
&
. Oto przykładowy program wykorzystujący struktury w celu wykonania
prostych operacji na pliku listy adresów:
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
230
Hack Wars. Tom 1. Na tropie hakerów
230
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>
/* liczba_wierszy to liczba wierszy ekranu */
#define liczba_wierszy 25
typedef struct
{
char nazwisko[30];
char adres[30];
char miasto[30];
char wojewodztwo[30];
char kod[6];
char nrtelefonu[15];
}
dane;
dane rekord;
int handle;
/* Prototypy funkcji */
void ADD_REC(void);
void CLS(void);
void DISPDATA(void);
void FATAL(char *);
void GETDATA(void);
void MENU(void);
void OPENDATA(void);
int SEARCH(void);
void CLS()
{
int n;
for(n = 0; n < liczba_wierszy; n++)
puts("");
}
void FATAL(char *blad)
{
printf(" \nBlad krytyczny: %s",blad);
exit(0);
}
void OPENDATA()
{
/* Sprawd
ź
czy istnieje plik danych. Je
Ŝ
eli nie, utwórz. */
/* Je
Ŝ
eli tak, otwórz do odczytu-zapisu na ko
ń
cu pliku. */
handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE);
if (handle == -1)
{
handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE);
if (handle == -1)
FATAL("Nie mo
Ŝ
na utworzy
ć
pliku danych");
}
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
231
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
231
void GETDATA()
{
/* Pobierz dane adresowe */
CLS();
printf("Nazwisko ");
gets(rekord.nazwisko);
printf("\nAdres ");
gets(rekord.adres);
printf("\nMiasto ");
gets(rekord.miasto);
printf("\nWojewództwo ");
gets(rekord.wojewodztwo);
printf("\nKod pocztowy ");
gets(rekord.kod);
printf("\nNumer telefonu ");
gets(rekord.nrtelefonu);
}
void DISPDATA()
{
/* Wy
ś
wietl dane adresowe */
char tekst[5];
CLS();
printf("Nazwisko %s",rekord.nazwisko);
printf("\nAdres %s",rekord.adres);
printf("\nMiasto %s",rekord.miasto);
printf("\nWojewództwo %s",rekord.wojewodztwo);
printf("\nKod pocztowy %s",rekord.kod);
printf("\nNumer telefonu %s\n\n",rekord.nrtelefonu);
puts(" Wci
ś
nij ENTER");
gets(tekst);
}
void ADD_REC()
{
/* Doł
ą
cz do pliku danych nowy rekord */
int wynik;
wynik = write(handle,&rekord,sizeof(dane));
if (wynik == -1)
FATAL("Zapis do pliku danych niemo
Ŝ
liwy");
}
int SEARCH()
{
char tekst[100];
int wynik;
printf("Wprowad
ź
wzór wyszukiwania ");
gets(tekst);
if (*tekst == 0)
return(-1);
/* Zlokalizuj pocz
ą
tek pliku */
lseek(handle,0,SEEK_SET);
do
232
Hack Wars. Tom 1. Na tropie hakerów
232
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
{
/* Załaduj rekord do pami
ę
ci */
wynik = read(handle,&rekord,sizeof(dane));
if (wynik > 0)
{
/* Przeszukaj rekord */
if (strstr(rekord.nazwisko,tekst) != NULL)
return(1);
if (strstr(rekord.adres,tekst) != NULL)
return(1);
if (strstr(rekord.miasto,tekst) != NULL)
return(1);
if (strstr(rekord.wojewodztwo,tekst) != NULL)
return(1);
if (strstr(rekord.kod,tekst) != NULL)
return(1);
if (strstr(rekord.nrtelefonu,tekst) != NULL)
return(1);
}
}
while(wynik > 0);
return(0);
}
void MENU()
{
int opcja;
char tekst[10];
do
{
CLS();
puts("\n\t\t\tWybierz opcj
ę
");
puts("\n\n\t\t\t1 Dodaj nowy rekord");
puts("\n\n\t\t\t2 Przeszukiwanie danych");
puts("\n\n\t\t\t3 Wyj
ś
cie");
puts("\n\n\n\n\n");
gets(tekst);
opcja = atoi(tekst);
switch(opcja)
{
case 1 : GETDATA();
/* Przed doł
ą
czaniem rekordu przejd
ź
do ko
ń
ca pliku */
lseek(handle,0,SEEK_END);
ADD_REC();
break;
case 2 : if (SEARCH())
DISPDATA();
else
{
puts("NIEZNALEZIONE!");
puts("Wci
ś
nij ENTER");
gets(tekst);
}
break;
case 3 : break;
}
}
while(opcja != 3);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
233
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
233
}
void main()
{
CLS();
OPENDATA();
MENU();
}
Pola bitowe
Język C przewiduje moŜliwość korzystania w strukturach ze zmiennych o rozmiarze
mniejszym niŜ 8 bitów. Określa się je mianem pól bitowych, a ich rozmiar moŜe być
dowolny, od 1 bitu wzwyŜ. Deklaracja pola bitowego wygląda następująco:
typ nazwa : liczba_bitów;
Przykładem moŜe być deklaracja kilku jednobitowych znaczników stanu:
typedef struct
{
unsigned przeniesienie : 1;
unsigned zero : 1;
unsigned przepelnienie : 1;
unsigned parzystosc : 1;
}
df;
df znaczniki;
Zmienna
znaczniki
będzie zajmować w pamięci tylko 4 bity, mimo Ŝe składa się z 4
pól, z których kaŜde dostępne jest jako osobne pole struktury.
Union
Kolejnym ułatwieniem języka C, pozwalającym zapewnić optymalne wykorzystanie
dostępnej pamięci, jest struktura
union
, czyli zbiór zmiennych, współuŜytkujących
jeden adres pamięci. Oznacza to, oczywiście, Ŝe w danym momencie dostępna jest
tylko jedna ze zmiennych składowych. Deklaracja
union
ma następującą postać:
union nazwa
{
typ nazwa_zmiennej;
typ nazwa_zmiennej;
.
.
.
typ nazwa_zmiennej;
};
Wyliczenia
Wyliczenie (enumeracja) to przypisanie liście symboli rosnących wartości
całkowitych. Wyliczenie deklarujemy:
enum nazwa { lista } lista_zmiennych;
234
Hack Wars. Tom 1. Na tropie hakerów
234
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Przykładem moŜe być definicja listy kolorów:
enum KOLORY
{
CZARNY,
NIEBIESKI,
ZIELONY,
CZERWONY,
BRAZOWY,
JASNOSZARY,
CIEMNOSZARY,
JASNONIEBIESKI,
JASNOZIELONY,
JASNOCZERWONY,
ZOLTY,
BIALY
};
Operacje na plikach
W operacjach dostępu do plików język C posługuje się buforowanymi strumieniami
plikowymi. Niektóre z platform języka, jak UNIX i DOS, oferują równieŜ
niebuforowane uchwyty plików.
Strumienie buforowane
Dostęp do strumieni buforowanych realizowany jest za pośrednictwem wskaźnika do
zmiennej typu
FILE
. Ten szczególny typ danych zdefiniowany został w nagłówku
stdio.h. Aby więc zadeklarować wskaźnik do pliku, wprowadzamy:
#include <stdio.h>
FILE *ptr;
Aby otworzyć strumień, uŜywamy funkcji
fopen()
. Pobiera ona dwa parametry:
nazwę otwieranego pliku oraz tryb dostępu. Oto lista trybów dostępu.
Tryb
Opis
r
otwórz tylko do odczytu (plik musi istnieć),
w
utwórz do zapisu; zastąp, jeŜeli plik o podanej nazwie istnieje,
a
otwórz do dołączania danych (dopisywania na końcu pliku); utwórz nowy plik,
jeŜeli plik o podanej nazwie nie istnieje,
r+
otwórz istniejący plik do odczytu i zapisu (plik musi istnieć),
w+
utwórz do odczytu i zapisu; zastąp, jeŜeli istnieje,
a+
otwórz do czytania i dołączania danych; utwórz nowy plik, jeŜeli nie istnieje.
Aby określić tryb tekstowy lub binarny, do opisu trybu moŜna dołączyć
t
lub
b
. W
przypadku pominięcia tego znacznika strumień zostanie otwarty w trybie określanym
zmienną globalną
_fmode
. Odczyt i zapis danych do strumieni plikowych w trybie
tekstowym wiąŜe się z konwersją — podczas zapisu znaki CR i LF zamieniane są na
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
235
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
235
pary CR LF, a przy odczycie pary CR LF ulegają zamianie na pojedynczy znak LF.
Tego rodzaju operacje nie są wykonywane w trybie binarnym.
JeŜeli funkcja
fopen()
nie będzie mogła otworzyć pliku, zwróci w miejsce
wskaźnika wartość
NULL
(zdefiniowaną w stdio.h). PoniŜszy program utworzy nowy
plik dane.txt i udostępni go do odczytu i zapisu:
#include <stdio.h>
void main()
{
FILE *fp;
fp = fopen("dane.txt","w+");
}
Aby zamknąć strumień, uŜywamy funkcji
fclose()
, wymagającej podania
wskaźnika do pliku.
fclose(fp);
JeŜeli podczas zamykania strumienia wystąpi błąd, funkcja
fclose()
zwróci wartość
niezerową (znacznik EOF — End Of File). Do przesyłania i odbierania danych ze
strumieni słuŜą cztery podstawowe funkcje:
fgetc()
,
fputc()
,
fgets()
i
fputs()
. Funkcja
fgetc()
odczytuje pojedynczy znak z określonego strumienia
wejściowego (przekształcany do liczby całkowitej):
int fgetc(FILE *fp);
Jej odwrotnością jest
fputc()
, zapisująca pojedynczy znak do określonego
strumienia wyjściowego:
int fputc( int c, FILE *fp);
Funkcja
fgets()
odczytuje ze strumienia wejściowego ciąg:
char *fgets(char *s, int liczba_bajtów, FILE *fp);
Odczyt zostaje przerwany po pobraniu
liczba_bajtów-1
znaków lub znaku
nowego wiersza (równieŜ wstawianego do tablicy). Do odczytanego ciągu
s
dołączany
jest kończący znak
NULL (znany tak
Ŝ
e pod postaci
ą
’\0’)
. W przypadku
wystąpienia błędów funkcja zwraca
NULL
.
Funkcja
fputs()
zapisuje do strumienia ciąg zakończony znakiem
NULL
(inaczej
’\0’
):
int fputs(const char *s, FILE *fp);
Wszystkie opisywane funkcje zwracają w przypadku błędów wartość
EOF
(zdefiniowaną w stdio.h) z wyjątkiem funkcji
fgets()
, która w przypadku
wystąpienia błędu zwraca
NULL
. PoniŜszy program tworzy kopię pliku dane.dat, o
nazwie dane.old, ilustrując zarazem uŜycie wszystkich czterech funkcji:
#include <stdio.h>
236
Hack Wars. Tom 1. Na tropie hakerów
236
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
int main()
{
FILE *in;
FILE *out;
in = fopen("data.dat","r");
if (in == NULL)
{
puts("\nNie mo
Ŝ
na otworzy
ć
do odczytu pliku dane.dat");
return(0);
}
out = fopen("dane.old","w+");
if (out == NULL)
{
puts("\nNie mo
Ŝ
na utworzy
ć
pliku dane.old");
return(0);
}
/* Powtarzaj odczytywanie i zapisywanie pojedynczych bajtów */
/* a
Ŝ
do natrafienia na EOF */
while(!feof(in))
fputc(fgetc(in),out);
/* Zamknij strumienie plikowe */
fclose(in);
fclose(out);
return(0);
}
W kolejnym przykładowym programie uŜywamy funkcji
fputs
do kopiowania tekstu
ze strumienia
stdin
(zazwyczaj oznacza to znaki wprowadzane z klawiatury) do
nowego pliku dane.txt:
#include <stdio.h>
int main()
{
FILE *fp;
char tekst[100];
fp = fopen("dane.txt","w+");
do
{
gets(tekst);
fputs(tekst,fp);
}
while(*tekst);
fclose(fp);
}
Swobodny dostęp do danych strumieni
Dostęp swobodny do danych dostarczanych za pośrednictwem strumieni zapewnia
funkcja
fseek()
o prototypie:
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
237
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
237
int fseek(FILE *fp, long liczba_bajtów, int zacznij_od);
Funkcja zmienia pozycję wskaźnika pliku skojarzonego ze strumieniem otwartym
wcześniej przez
fopen()
. Wskaźnik ustawiany jest na
liczba_bajtów
za (lub
przed w przypadku wartości ujemnej) pozycją
zacznij_od
. Tą ostatnią moŜe być
początek pliku, bieŜące połoŜenie wskaźnika lub koniec pliku. Pozycje te symbolizują
stałe
SEEK_SET
,
SEEK_CUR
i
SEEK_END
. Udaną operację
fseek()
sygnalizuje
zwrócenie wartości
0
. Uzupełnieniem
fseek()
jest funkcja
ftell()
, zwracająca
wartość bieŜącej pozycji wskaźnika pliku:
long int ftell(FILE *fp);
Funkcja zwraca pozycję wskaźnika pliku, określoną jako ilość bajtów od początku
pliku, lub
-1
w przypadku błędu.
Uchwyty
Uchwyty plików (handles) otwiera funkcja
open()
o prototypie:
int open(char *nazwa_pliku, int dost
ę
p[, unsigned tryb]);
Udaną operację sygnalizuje zwrócenie numeru uchwytu. W pozostałych przypadkach
zwracane jest
–1
. Na wartość
dost
ę
p
składają się połączone bitową operacją OR
stałe symboliczne, odpowiadające deklaracjom w pliku fcntl.h. RóŜnią się one w
zalezności od kompilatora. Do typowych naleŜą:
O_APPEND
przed kaŜdym zapisem wskaźnik pliku będzie ustawiany na końcu pliku,
O_CREAT
jeŜeli plik nie istnieje, zostanie utworzony,
O_TRUNC
obcina istniejący plik do długości 0 bajtów,
O_EXCL
uŜywane w połączeniu z
O_CREAT
,
O_BINARY
otwiera plik w trybie binarnym,
O_TEXT
otwiera plik w trybie tekstowym.
Po przypisaniu uchwytu pliku za pomocą polecenia
open()
moŜna korzystać z
funkcji
read()
i
write()
. Prototyp
read()
jest następujący:
int read(int handle, void *buf, unsigned liczba_bajtów);
Funkcja podejmuje próbę odczytu podanej liczby bajtów i zwraca liczbę bajtów
faktycznie pobranych przez uchwyt pliku. Odczytane dane umieszczane są w bloku
pamięci określonym parametrem
buf
. Funkcja
write()
działa podobnie, nie róŜni
się równieŜ jej prototyp i sposób generowania wartości zwracanej. Zapisuje ona
podaną ilość bajtów z określonego wskaźnikiem bloku pamięci. Pliki otwierane
funkcją
open()
zamykamy funkcją
close()
:
int close(int handle);
Funkcja
close()
zwraca
0
w przypadku operacji udanej, a
–1
w przypadku
wystąpienia błędów.
238
Hack Wars. Tom 1. Na tropie hakerów
238
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Dostęp swobodny zapewnia funkcja
lseek()
, bardzo podobna do
fseek()
, ale
pobierająca jako parametr numer uchwytu, a nie wskaźnik strumienia
FILE
. W
poniŜszym przykładzie wykorzystujemy uchwyt pliku do zapisu danych z
stdin
(czyli klawiatury) do nowego pliku o nazwie dane.txt:
#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>
int main()
{
int handle;
char tekst[100];
handle = open("dane.txt", O_RDWR|O_CREAT|O_TRUNC,S_IWRITE);
do
{
gets(tekst);
write(handle, &tekst, strlen(tekst));
}
while(*tekst);
close(handle);
}
Przegląd funkcji plikowych
Norma ANSI definiuje związane z plikami operacje we-wy przy uŜyciu strumieni,
opisując róŜnorodne funkcje. Prototyp funkcji
fopen()
ma postać:
FILE *fopen(const char *nazwa, const char *tryb);
Funkcja podejmuje próbę otwarcia strumienia łączącego z plikiem o podanej nazwie
w określonym trybie. Udana operacja kończy się zwróceniem wskaźnika typu
FILE
.
W przypadku niepowodzenia funkcji zwraca
NULL
. Na wcześniejszych stronach
przedstawiony został opis parametru
tryb
.
Funkcja
fclose()
słuŜy do zamykania strumienia otwartego wcześniejszym
wywołaniem
fopen()
:
int fclose(FILE *fp);
Udana operacja
fclose()
kończy się opróŜnieniem wszystkich buforów pliku i
zwróceniem wartości
0
. W przypadku błędów zwracana jest wartość
EOF
.
Wiele komputerów korzysta z buforowanego dostępu do plików. Oznacza to, Ŝe dane,
zapisywane do strumienia, wstępnie umieszczane są w pamięci, a faktyczny zapis
następuje dopiero po przekroczeniu pewnej granicznej ilości bajtów. JeŜeli w czasie,
gdy dane nie zostały jeszcze faktycznie zapisane do strumienia, nastąpi awaria
zasilania, dane zostaną utracone. Zabezpiecza przed tym funkcja
fflush()
,
wymuszająca zapisanie wszystkich danych oczekujących:
int fflush(FILE *fp);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
239
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
239
JeŜeli wywołanie
fflush()
jest udane, związane ze strumieniem bufory zostają
opróŜnione i zwracana jest wartość
0
. W przypadku błędów funkcja zwraca wartość
EOF
.
Kolejną funkcją jest
ftell()
zwracająca lokalizację wskaźnika pliku:
long int ftell(FILE *fp);
Funkcja zwraca przesunięcie wskaźnika pliku w stosunku do początku pliku lub
–1L
w przypadku błędów. Przesunięcie wskaźnika pliku do nowej pozycji umoŜliwia
fseek()
:
int fseek(FILE *fp, long offset, int zacznij_od);
Funkcja podejmuje próbę przesunięcia wskaźnika pliku o
offset
bajtów od pozycji
zacznij_od
, określonej jedną ze stałych:
SEEK_SET
początek pliku,
SEEK_CUR
bieŜąca pozycja wskaźnika pliku,
SEEK_END
koniec pliku.
Przesunięcie (
offset
) moŜe być wartością dodatnią (przesuwanie wskaźnika w
stronę końca pliku) lub ujemną (przesuwanie wskaźnika w stronę początku pliku).
Aby szybko przenieść wskaźnik do początku pliku i usunąć wcześniejsze odwołania
do błędów, C dostarcza funkcji
rewind()
:
void rewind(FILE *fp);
Funkcja ta działa podobnie jak
fseek(fp,0L,SEEK_SET)
. Jednak
fseek()
usuwa
znacznik
EOF
, a
rewind() dodatkowo
wszystkie sygnały błędów. Informacje o
błędach funkcji plikowych moŜna pobrać przy uŜyciu funkcji
ferror()
:
int ferror (FILE *fp);
Funkcja zwraca wartość niezerową, jeŜeli w określonym strumieniu wystąpił błąd. Po
sprawdzeniu wartości
ferror()
naleŜy zadbać o usunięcie sygnałów błędów za
pomocą funkcji
clearerr()
:
void clearerr(FILE *fp);
Sprawdzenie, czy spełniony jest warunek osiągnięcia końca pliku, realizuje
predefiniowane makro
feof()
:
int feof(FILE *fp);
Makro zwraca wartość niezerową, gdy dla danego strumienia stwierdzono osiągnięcie
końca pliku. W pozostałych przypadkach zwracaną wartością jest
0
.
Dostępnych jest kilka funkcji realizujących odczyt danych ze strumienia plikowego.
Pojedyncze znaki moŜna odczytywać funkcją
fgetc()
:
int fgetc(FILE *fp);
240
Hack Wars. Tom 1. Na tropie hakerów
240
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
fgetc()
zwraca wartość ASCII pobranego znaku lub znak
EOF
w przypadku
wystąpienia błędu. Odczyt ciągu danych umoŜliwia funkcja
fgets()
, odczytująca
ciąg zakończony znakiem nowego wiersza:
char *fgets(char *s, int n, FILE *fp);
W wyniku udanego wywołania funkcji w zmiennej
s
umieszczany jest ciąg
zakończony znakiem nowego wiersza lub zawierający
n-1
znaków. Funkcja
zachowuje kończący ciąg znak nowego wiersza, dołączając do ciągu
s
bajt
NULL
. W
przypadku nieudanego wywołania zwracany jest wskaźnik pusty. Ciągi zapisujemy
do strumienia funkcją
fputs()
:
int fputs(const char *s, FILE *fp);
Funkcja
fputs()
zapisuje wszystkie znaki ciągu
s
, z wyjątkiem końcowego bajtu
NULL
, do strumienia
fp
. Standardowo funkcja zwraca ostatni zapisany znak, a w
przypadku wystąpienia błędów —
EOF
. Dostępna jest równieŜ funkcja zapisująca do
strumienia pojedynczy znak
fputc()
:
int fputc(int c, FILE *fp);
Funkcja zwraca zapisany znak lub, w przypadku wystąpienia błędów, znak
EOF
.
Aby odczytać ze strumienia duŜy blok danych lub rekord, moŜna posłuŜyć się funkcją
fread()
:
size_t fread(void *ptr, size_t rozmiar, size_t n, FILE *fp);
Funkcja podejmuje próbę odczytu
n
elementów, z których kaŜdy ma długość
rozmiar
, ze strumienia plikowego
fp
do bloku pamięci określonego wskaźnikiem
ptr
. Aby ustalić, czy operacja przebiegła bez zakłóceń, korzystamy z funkcji
ferror()
.
Siostrzaną funkcją
fread()
jest
fwrite()
:
size_t fwrite(const void *ptr, size_t rozmiar, size_t n, FILE *fp);
Funkcja zapisuje
n
elementów o długości
rozmiar
z obszaru pamięci określonego
wskaźnikiem
ptr
do strumienia
fp
.
Funkcja
fscanf()
umoŜliwia odczyt danych formatowanych:
int fscanf(FILE *fp, const char *format[,adres ...]);
Funkcja zwraca liczbę faktycznie odczytanych pól, a
EOF
w przypadku końca pliku.
PoniŜszy przykład ilustruje uŜyteczność funkcji
fscanf()
podczas odczytywania ze
strumienia liczb:
#include <stdio.h>
void main()
{
FILE *fp;
int a;
int b;
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
241
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
241
int c;
int d;
int e;
char tekst[100];
fp = fopen("dane.txt","w+");
if(!fp)
{
perror("Nie mo
Ŝ
na utworzy
ć
pliku");
exit(0);
}
fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\"");
fflush(fp);
if (ferror(fp))
{
fputs("Bł
ą
d przy zapisie strumienia", stderr);
exit(1);
}
rewind(fp);
if (ferror(fp))
{
fputs("Bł
ą
d przy przewijaniu strumienia", stderr);
exit(1);
}
fscanf(fp,"%d %d %d %d %d %s", &a, &b, &c, &d, &e, tekst);
if (ferror(fp))
{
fputs("Bł
ą
d odczytu ze strumienia", stderr);
exit(1);
}
printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d
%s",a,b,c,d,e,tekst);
}
Jak łatwo zauwaŜyć, zapis formatowanych danych realizuje funkcja
fprintf()
. Gdy
pojawia się potrzeba zapisania połoŜenia wskaźnika pliku i późniejszego jego
przywrócenia, moŜna skorzystać z funkcji
fgetpos()
i
fsetpos()
. Pierwsza z nich
odczytuje bieŜącą pozycję wskaźnika pliku:
int fgetpos(FILE *fp, fpos_t *pozycja);
Funkcja
fsetpos()
ustawia wskaźniki pliku na określonej pozycji:
int fsetpos(FILE *fp, const fpos_t *pozycja);
Typ
fpos_t
zdefiniowany został w nagłówku stdio.h. Funkcje te są wygodniejsze
w uŜyciu niŜ
ftell()
i
fseek()
.
Z otwartym juŜ strumieniem moŜna skojarzyć nowy plik. UmoŜliwia to funkcja
freopen()
:
FILE *freopen(const char *nazwa, const char *tryb, FILE *fp);
242
Hack Wars. Tom 1. Na tropie hakerów
242
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Funkcja zamyka strumień istniejący i podejmuje próbę jego ponownego otwarcia przy
uŜyciu podanej nazwy pliku. Znajduje to zastosowanie przy przekierowywaniu
strumieni predefiniowanych
stdin
,
stdout
i
stderr
do pliku lub urządzenia.
Przykładowo, gdy pojawia się potrzeba przekierowania wszystkich danych
wyjściowych kierowanych do
stdout
na drukarkę, moŜna uŜyć polecenia:
freopen("LPT1","w",stdout);
Predefiniowane strumienie we-wy
Wstępnie zdefiniowane zostały trzy strumienie we-wy:
stdin
,
stdout
i
stderr
.
Domyślnie
stdin
i
stdout
odpowiadają klawiaturze i monitorowi. Na wielu
platformach, w tym systemów DOS i UNIX, dostępna jest moŜliwość ich
przekierowania. Strumień
stderr
domyślnie powiązany jest z monitorem
(wyświetlaczem). Praktyka jego przekierowywania nie jest raczej stosowana. Jego
podstawowym zadaniem jest zapewnienie moŜliwości wyświetlania komunikatów
błędów, nawet w sytuacji gdy powiązanie standardowego wyjścia (
stdout
) zostało
zmienione:
fputs("Komunikat o bł
ę
dzie", stderr);
Funkcje
printf()
i
puts()
przekazują dane do strumienia
stdout
. Funkcje
scanf()
i
gets()
pobierają dane ze strumienia
stdin
. Przekierowanie tych
strumieni zmienia sposób działania funkcji.
Jako przykład plikowych operacji we-wy na platformie PC, korzystających z
moŜliwości przekierowania strumieni, przedstawimy prosty program przesyłający do
strumienia
stdout
zawartość określonego pliku, przedstawioną jako wartości
szesnastkowe. Polecenie w postaci:
dump nazwa_pliku.xxx > dane_wyj
ś
ciowe.xxx
pozwoli zmienić domyślne powiązanie strumienia
stdout
z monitorem.
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <string.h>
main(int argc, char *argv[])
{
unsigned licznik;
unsigned char v1[20];
int f1;
int x;
int n;
if (argc != 2)
{
fputs("\nBŁ
Ą
D. Poprawna składnia wywołania: dump f1\n",stderr);
return(1);
}
f1 = open(argv[1],O_RDONLY);
if (f1 == -1)
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
243
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
243
fprintf(stderr, "\nBŁ
Ą
D. Nie mo
Ŝ
na otworzy
ć
%s\n",argv[1]);
return(1);
}
fprintf(stdout,"\nZAWARTO
ŚĆ
PLIKU %s\n\n",strupr(argv[1]));
licznik = 0;
while(1)
{
/* Wypełnienie bufora zerami */
memset(v1,0,20);
/* Pobranie do bufora danych z pliku */
x = _read(f1,&v1,16);
/* x = 0 to EOF, x = -1 oznacza bł
ą
d */
if (x < 1)
break;
/* Wyprowad
ź
offset w pliku */
fprintf(stdout,"%06d(%05x) ",licznik,licznik);
licznik +=16;
/* Wyprowad
ź
szesnastkowe warto
ś
ci bajtów z bufora */
for(n = 0; n < 16; n++)
fprintf(stdout,"%02x ",v1[n]);
/* Wyprowad
ź
warto
ś
ci ASCII bajtów z bufora */
for(n = 0; n < 16; n++)
{
if ((v1[n] > 31) && (v1[n] < 128))
fprintf(stdout,"%c",v1[n]);
else
fputs(".",stdout);
}
/* Zako
ń
cz znakiem nowego wiersza */
fputs("\n",stdout);
}
/* zako
ń
czenie normalne */
return(0);
}
Ciągi
Język C naleŜy do najlepiej wyposaŜonych w funkcje obsługi ciągów pośród
uniwersalnych języków programowania. Ciąg to jednowymiarowa tablica znaków
zakończona bajtem zerowym. Ciągi moŜna inicjować dwoma sposobami. Pierwszym
jest nadanie im stałej wartości w kodzie programu:
int main()
{
char *p = "System 5";
char nazwa[] = "Program testowy";
return(0);
}
244
Hack Wars. Tom 1. Na tropie hakerów
244
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Drugi to utworzenie ciągu w czasie wykonywania programu za pomocą funkcji
strcpy()
:
char *strcpy(char *cel,const char *
ź
ródło);
Funkcja
strcpy()
kopiuje ciąg źródłowy do lokalizacji docelowej, na przykład:
#include <stdio.h>
int main()
{
char nazwa[50];
strcpy(nazwa,"Servile Software");
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
return 0;
}
Język C umoŜliwia bezpośredni dostęp do kaŜdego bajtu ciągu:
#include <stdio.h>
int main()
{
char nazwa[50];
strcpy(nazwa,"Servile Software");
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
/* Zast
ą
pienie pierwszego bajtu liter
ą
's' */
nazwa[0] = 's';
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
return 0;
}
Niektóre kompilatory C wyposaŜone zostały w funkcje konwersji ciągów do wielkich
i małych liter, nie obejmuje ich jednak norma ANSI. W specyfikacji pojawiają się za
to funkcje
toupper()
i
tolower()
, zwracające pojedynczy znak ( w postaci wartości
int
) zamieniony na literę wielką lub małą. Łatwo na tej podstawie utworzyć własne
funkcje konwersji ciągów:
#include <stdio.h>
void strupr(char *zrodlo)
{
char *p;
p = zrodlo;
while(*p)
{
if((*p)>=97 && (*p)<=122)
*p = toupper(*p);
p++;
}
}
void strlwr(char *zrodlo)
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
245
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
245
{
char *p;
p = zrodlo;
while(*p)
{
if((*p)>=65 && (*p)<=90)
*p = tolower(*p);
p++;
}
}
int main()
{
char nazwa[50];
strcpy(nazwa,"Servile Software");
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
strupr(nazwa);
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
strlwr(nazwa);
printf("\nWarto
ść
ci
ą
gu 'nazwa' to %s",nazwa);
return 0;
}
(To niezupełnie tak. Funkcje toupper i tolower tworz
ą
litery wielkie
i małe nie sprawdzaj
ą
c, jaka jest posta
ć
ź
ródłowa. Konwersja odbywa
si
ę
odpowiednio poprzez odj
ę
cie lub dodanie do warto
ś
ci znaku 32 (bo
taka jest ró
Ŝ
nica pomi
ę
dzy odpowiadaj
ą
cymi sobie literami wielkimi i
małymi). Je
ś
li argument funkcji toupper b
ę
dzie ju
Ŝ
liter
ą
wielk
ą
,
wynik konwersji oka
Ŝ
e si
ę
bezsensowny. W tym przypadku ‘S’ zostanie
zamienione na ‘3’). W funkcjach strlwr i strupr potrzebne byłoby wi
ę
c
sprawdzanie, czy znak spełnia kryteria, np.:
while (*p)
{
if((*p)>=97 && (*p)<=122)
*p = toupper(*p);
p++;
}
oraz
while (*p)
{
if((*p)>=65 && (*p)<=90)
*p = tolower(*p);
p++;
}
Dodatkowe linie programu wstawiłem w kod P.B.).
W przeciwieństwie do innych języków programowania C nie narzuca ograniczenia
długości ciągu. Jednak w przypadku niektórych procesorów (CPU) pojawia się
ograniczenie wielkości bloku pamięci. Oto prosty program odwracający kolejność
znaków w ciągu:
#include <stdio.h>
#include <string.h>
246
Hack Wars. Tom 1. Na tropie hakerów
246
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
char *strrev(char *s)
{
/* Odwraca kolejno
ść
znaków w ci
ą
gu, pozostawiaj
ą
c jedynie */
/* ko
ń
cowy znak NULL */
char *pocz;
char *koniec;
char tmp;
/* Ustaw wska
ź
nik 'koniec' na ostatni znak ci
ą
gu */
koniec = s + strlen(s) - 1;
/* Zabezpiecz wska
ź
nik do pocz
ą
tku ci
ą
gu */
pocz = s;
/* Zamiana */
while(koniec >= s)
{
tmp = *koniec;
*koniec = *s;
*s = tmp;
koniec--;
s++;
}
return(pocz);
}
void main()
{
char tekst[100];
char *p;
strcpy(tekst,"To jest ci
ą
g");
p = strrev(tekst);
printf("\n%s",p);
}
strtok()
Funkcja strtok() jest istotną funkcją języka C, słuŜącą do wyłączania fragmentów
ciągu. Stosuje się ją, gdy poszczególne podciągi rozdzielone są znanymi
ogranicznikami, na przykład przecinkami:
#include <stdio.h>
#include <string.h>
void main()
{
char dane[50];
char *p;
strcpy(dane,"CZERWONY,POMARA
Ń
CZOWY,
ś
ÓŁTY,ZIELONY,NIEBIESKI");
p = strtok(dane,",");
while(p)
{
puts(p);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
247
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
247
p = strtok(NULL,",");
};
}
Program moŜna oprzeć teŜ na pętli
for()
:
#include <stdio.h>
#include <string.h>
void main()
{
char dane[50];
char *p;
strcpy(dane,"CZERWONY,POMARA
Ń
CZOWY,
ś
ÓŁTY,ZIELONY,NIEBIESKI");
for( p = strtok(dane,","); p; p = strtok(NULL,","))
{
puts(p);
};
}
W pierwszym wywołaniu funkcji
strtok()
podajemy nazwę zmiennej ciągu oraz
ogranicznik. Funkcja zwraca wówczas wskaźnik do początku pierwszego podciągu
i zastępuje pierwszy ogranicznik zerem. Kolejne wywołania
strtok()
wykonywane są
w pętli. Pierwszym parametrem jest wówczas
NULL
, a funkcja zwraca kolejne
podciągi. PoniewaŜ dopuszczalne jest podanie listy ograniczników, funkcja
strtok()
moŜe posłuŜyć do utworzenia prostego programu zliczającego słowa:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(int argc, char *argv[])
{
FILE *fp;
char bufor[256];
char *p;
long licznik;
if (argc != 2)
{
fputs("\nBŁ
Ą
D. Poprawna składnia wywołania: wordcnt
f1\n",stderr);
exit(0);
}
/* Otwórz plik do odczytu */
fp = fopen(argv[1],"r");
/* Sprawd
ź
czy plik został otwarty */
if (!fp)
{
fputs("\nBŁ
Ą
D. Nie mo
Ŝ
na otworzy
ć
pliku
ź
ródłowego\n",stderr);
exit(0);
}
/* Inicjuj licznik */
licznik = 0;
do
248
Hack Wars. Tom 1. Na tropie hakerów
248
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
{
/* Odczytaj z pliku wiersz danych */
fgets(bufor, 255, fp);
/* Sprawd
ź
czy nie wyst
ą
pił bł
ą
d lub znak EOF */
if (ferror(fp) || feof(fp))
continue;
/* Zlicz słowa w pobranym wierszu */
/* Słowa wyró
Ŝ
nia si
ę
jako elementy rozdzielone znakami */
/* \t (tab) \n (nowy wiersz) , ; : . ! ? ( ) - spacja */
p = strtok(bufor, "\t\n,;:.!?()- ");
while(p)
{
licznik++;
p = strtok(NULL,"\t\n,;:.!?()- ");
}
}
while(!ferror(fp) && !feof(fp));
/* Odczyt zako
ń
czony. Bł
ą
d? */
if (ferror(fp))
{
fputs("\nBł
ą
d przy odczycie pliku
ź
ródłowego\n",stderr);
fclose(fp);
exit(0);
}
/* Odczyt zako
ń
czony poprawnie, znakiem EOF */
/* Wyprowadzamy liczb
ę
słów */
printf("\nPlik %s zawiera %ld słów(słowa)\n",argv[1],licznik);
fclose(fp);
}
Zamiana liczb na ciągi i ciągów na liczby
Wszystkie kompilatory C zapewniają moŜliwość konwertowania liczb na ciągi przy
uŜyciu takich funkcji jak
sprintf()
. Funkcja ta ma jednak wiele zastosowań, co
powoduje, Ŝe jest rozbudowana i mało wydajna. MoŜe ją zastępować funkcja
ITOS()
, korzystająca z dwóch parametrów: liczby całkowitej ze znakiem i wskaźnika
do ciągu znakowego. Funkcja kopiuje liczbę do określonego wskaźnikiem miejsca w
pamięci. Podobnie jak
sprintf()
, funkcja
ITOS()
nie sprawdza, czy ciąg docelowy
ma wystarczającą do przechowania wyniku konwersji długość. Oto przykładowa
funkcja, która kopiuje liczbę
signed int
do ciągu znakowego.
void ITOS(long x, char *ptr)
{
/*Zamie
ń
dziesi
ę
tn
ą
liczb
ę
całkowit
ą
ze znakiem na ci
ą
g znaków */
long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000,
100, 10, 1 };
int n;
/* Sprawd
ź
znak */
if (x < 0)
{
*ptr++ = '-';
/* Zamie
ń
x na warto
ść
bezwzgl
ę
dn
ą
*/
x = 0 - x;
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
249
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
249
for(n = 0; n < 9; n++)
{
if (x > pt[n])
{
*ptr++ = '0' + x / pt[n];
x %= pt[n];
}
}
*ptr='\0';
(zapewnia zako
ń
czenie ła
ń
cucha znakowego i zapobiega wypisywaniu
głupot, gdy
zmienna tablicowa ma wi
ę
kszy wymiar, ni
Ŝ
liczba tego potrzebuje
P.B.)
return;
}
(Powy
Ŝ
szy program działa nieprawidłowo, gdy w zamienianej liczbie
znajduj
ą
si
ę
zera. Poni
Ŝ
ej przedstawiam proponowan
ą
przeze mnie poprawn
ą
wersj
ę
P.B.):
void ITOS(long x, char *ptr)
{
/*Zamie
ń
dziesi
ę
tn
ą
liczb
ę
całkowit
ą
ze znakiem na ci
ą
g znaków */
long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000,
100, 10, 1 };
int n;
int licznik=0; //licznik potrzebny do zliczania zer na pocz
ą
tku
ci
ą
gu
/* Sprawd
ź
znak */
if (x < 0)
{
*ptr++ = '-';
licznik++;
/* Zamie
ń
x na warto
ść
bezwzgl
ę
dn
ą
*/
x = 0 - x;
}
for(n = 0; n < 9; n++)
{
licznik++;
*ptr++ = '0' + x / pt[n];
x %= pt[n];
}
*ptr='\0';
ptr=ptr-licznik; //powrót wska
ź
nika na pocz
ą
tek ci
ą
gu
licznik=0;
if(*ptr=='-') //omini
ę
cie minusa na pocz
ą
tku (je
ś
li jest)
ptr++;
while(*ptr=='0') { //pomijanie pocz
ą
tkowych zer
licznik++;
ptr++;
}
while(*ptr!='\0') {
*(ptr-licznik)=*ptr; //przepisywanie ci
ą
gu ju
Ŝ
bez zer na
pocz
ą
tku
ptr++;
}
*(ptr-licznik)='\0';
250
Hack Wars. Tom 1. Na tropie hakerów
250
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
return;
}
Język C oferuje dwie funkcje do zamiany ciągów znakowych na liczby
zmiennoprzecinkowe:
atof()
i
strtod()
. Prototyp funkcji
atof()
ma postać:
double atof(const char *s);
a prototyp funkcji
strtod()
:
double strtod(const char *s, char **endptr);
Obie funkcje przeglądają ciąg i przeprowadzają konwersję aŜ do momentu natrafienia
na niezrozumiały znak. RóŜnica między nimi polega na tym, Ŝe
strtod()
pobiera
dodatkowy parametr, wskaźnik
char
ustawiany na pierwszy znak ciągu, który nie
został objęty konwersją. Znacznie zwiększa to wygodę sprawdzania poprawności
wykonania operacji.
Aby zamienić ciąg na wartość całkowitą, moŜna uŜyć funkcji
atoi()
:
int atoi(const char *s);
NaleŜy pamiętać, Ŝe funkcja
atoi()
nie zapewnia Ŝadnej kontroli przepełnienia
zmiennej. Nie jest zdefiniowana wartość zwracana w takiej sytuacji. W podobny
sposób działa funkcja
atol()
, zwracająca wartość
long
. Odpowiedniki z
dodatkowym parametrem noszą nazwy
strol()
i
stroul()
.
Obsługa tekstu
Człowiek zapisuje informacje jako pewien „tekst”, złoŜony ze słów, liczb i znaków
przestankowych. Słowa złoŜone są z liter wielkich i małych, odpowiednio do
wymagań gramatyki. Wszystko to sprawia, Ŝe komputerowe przetwarzanie tekstu nie
jest zadaniem prostym. Norma ANSI definiuje wiele funkcji przetwarzania ciągów
znakowych, które z natury rozpoznają wielkość liter. Oznacza to, Ŝe litera „A”
rozpoznawana jest jako róŜna od „a”. Jest to pierwsze zagadnienie, którego
rozwiązanie musi znaleźć programista pracujący nad programem przetwarzającym tekst.
Na szczęście, zarówno kompilatory Borlanda, jak i Microsoftu wyposaŜone zostały w
funkcje obsługi ciągów, które nie rozpoznają wielkości liter.
Taką odmianą funkcji
strcmp()
jest
stricmp()
, a
strncmp()
—
strnicmp()
.
Gdy jednak pojawia się kwestia przenośności kodu, niezbędna jest zgodność z ANSI
C, co pociąga za sobą napisanie własnych funkcji.
PoniŜej przedstawiamy prostą implementację nierozróŜniającej wielkości liter
odmiany funkcji
strstr()
. Tworzy ona kopie ciągów, zamienia je na wielkie litery i
wykonuje standardową operację
strstr()
. Pozwala to określić poszukiwaną
wartość przesunięcia i utworzyć wskaźnik do ciągu źródłowego.
char *stristr(char *s1, char *s2)
{
char c1[1000];
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
251
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
251
char c2[1000];
char *p;
strcpy(c1,s1);
strcpy(c2,s2);
strupr(c1);
strupr(c2);
p = strstr(c1,c2);
if (p)
return s1 + (p - c1);
return NULL;
}
Kolejna funkcja przegląda ciąg
s1
, wyszukując słowo podane jako
s2
. Aby funkcja
zwróciła wartość
TRUE
, znalezione musi zostać odrębne słowo, a nie jedynie
sekwencja znaków. Wykorzystujemy przygotowaną wcześniej funkcję
stristr()
.
int word_in(char *s1, char *s2)
{
/*zwraca warto
ść
niezerow
ą
, je
Ŝ
eli s2 jest słowem zawartym w s1*/
char *p;
char *q;
int ok;
ok = 0;
q = s1;
do
{
/* Lokalizuj wyst
ą
pienie sekwencji znaków s2 w s1 */
p = stristr(q,s2);
if (p)
{
/* Znaleziony */
ok = 1;
if (p > s1)
{
/* Sprawd
ź
znak przed znalezionym ci
ą
giem*/
if (*(p-1) >= 'A' && *(p-1) <= 'z')
ok = 0;
}
/* Niech p wskazuje koniec ci
ą
gu */
p += strlen(s2);
if (*p)
{
/* Sprawd
ź
znak za znalezionym ci
ą
giem */
if (*p >= 'A' && *p <= 'z')
ok = 0;
}
}
q = p;
}
while(p && !ok);
return ok;
}
252
Hack Wars. Tom 1. Na tropie hakerów
252
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Szerokie zastosowanie znajdzie kilka dalszych prostych funkcji znakowych.
truncstr()
obcina ciąg znakowy:
void truncstr(char *p, int liczba)
{
/* Obcina 'liczba' znaków z ci
ą
gu 'p' */
if (liczba < strlen(p))
p[strlen(p) - liczba] = 0;
}
trim()
usuwa końcowe znaki spacji (odstępu międzywyrazowego) w ciągu:
void trim(char *tekst)
{
/* usuwa spacje ko
ń
cowe */
char *p;
p = &tekst[strlen(tekst) - 1];
while(*p == 32 && p >= tekst)
*p-- = 0;
}
strlench()
zmienia długość ciągu:
void strlench(char *p,int num)
{
/* Zmienia długo
ść
ci
ą
gu, doł
ą
czaj
ą
c lub usuwaj
ą
c znaki */
if (num > 0)
memmove(p + num,p,strlen(p) + 1);
else
{
num = 0 - num;
memmove(p,p + num,strlen(p) + 1);
}
}
strins()
umieszcza jeden ciąg w innym:
void strins(char *p, char *q)
{
/* Wstaw ci
ą
g q do ci
ą
gu p */
strlench(p,strlen(q));
strncpy(p,q,strlen(q));
}
strchg()
zastępuje wszystkie wystąpienia pewnego podciągu innym podciągiem:
void strchg(char *dane, char *s1, char *s2)
{
/* Zast
ę
puje wszystkie wyst
ą
pienia s1 ci
ą
giem s2 */
char *p;
char zmienione;
do
{
zmienione = 0;
p = strstr(dane, s1);
if (p)
{
/* Usu
ń
ci
ą
g znaleziony */
strlench(p, 0 - strlen(s1));
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
253
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
253
/* Wstaw ci
ą
g */
strins(p,s2);
zmienione = 1;
}
}
while(zmienione);
}
Data i godzina
Język C wyposaŜony jest w funkcję
time()
, która odczytuje zegar systemowy
komputera i podaje informację o dacie i godzinie w postaci liczby sekund, która
upłynęła od północy 1 stycznia 1970 roku. Wartość ta moŜe zostać zamieniona na
czytelny dla człowieka ciąg znaków za pomocą funkcji
ctime()
:
#include <stdio.h>
#include <time.h>
int main()
{
/* Struktura do przechowywania daty i godziny, z time.h */
time_t t;
/* Pobierz dat
ę
i godzin
ę
systemu */
t = time(NULL);
printf("Bie
Ŝą
ca data i godzina: %s\n",ctime(&t));
}
Na ciąg zwracany przez
ctime()
składa się siedem pól:
dzień tygodnia,
miesiąc roku,
dzień miesiąca,
godzina,
minuty,
sekundy,
rok.
Uzupełnieniem jest znak nowego wiersza i końcowe 0. PoniewaŜ pola mają stałą
szerokość, ciąg zwracany przez
ctime()
idealnie nadaje się do operacji
wymagających wyodrębnienia elementów daty lub godziny. W poniŜszym programie
definiujemy strukturę
godzina
oraz funkcję
pobierz_godzine()
, której zadaniem
jest wypełnienie struktury treścią pól ciągu
ctime()
:
#include <stdio.h>
#include <time.h>
#include <string.h>
struct godzina
{
int g_min; /* Minuty */
int g_godz; /* Godziny */
254
Hack Wars. Tom 1. Na tropie hakerów
254
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
int g_sek; /* Sekundy */
};
void pobierz_godzine(struct godzina *teraz)
{
time_t t;
char temp[26];
char *ts;
/* Pobierz dat
ę
i godzin
ę
systemu */
t = time(NULL);
/* Przedstaw dat
ę
i godzin
ę
w postaci ci
ą
gu */
strcpy(temp,ctime(&t));
/* Obetnij ostatnie pole */
temp[19] = 0;
ts = &temp[11];
/* Przeszukaj ci
ą
g i skopiuj elementy do struktury */
sscanf(ts, "%2d:%2d:%2d",&teraz->g_godz,&teraz->g_min,&teraz-
>g_sek);
}
int main()
{
struct godzina teraz;
pobierz_godzine(&teraz);
printf("\nJest godzina
%02d:%02d:%02d",&teraz.g_godz,&teraz.g_min,&teraz.g_sek);
}
Norma ANSI przewidziała równieŜ funkcję konwertującą wartość zwracaną przez
funkcję
time()
do postaci struktury. Przedstawiony poniŜej przykład zawiera
deklarację struktury
tm
z nagłówka
time.h
:
#include <stdio.h>
#include <time.h>
int main()
{
time_t t;
struct tm *tb;
/* Pobierz czas do t */
t = time(NULL);
/* Zamie
ń
warto
ść
t na struktur
ę
tb */
tb = localtime(&t);
printf("\nJest godzina %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb-
>tm_sec);
return (0);
}
Struktura
tm
(zawarta w pliku
time.h
) ma następującą postać:
struct tm
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
255
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
255
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
(Ta struktura nie mo
Ŝ
e by
ć
cz
ęś
ci
ą
programu, jak to zasugerowano, bo
jest ju
Ŝ
zdefiniowana w pliku nagłówkowym. W takiej sytuacji kompilator
wy
ś
wietla bł
ą
d. Mo
Ŝ
na j
ą
zostawi
ć
w tym miejscu z komentarzem,
który podałem na górze, wzgl
ę
dnie przenie
ść
na stron
ę
51 P.B.)
Liczniki czasu
Programy często korzystają z moŜliwości pobrania daty i czasu z nieulotnej pamięci
RAM komputera. Norma ANSI przewiduje kilka róŜnych funkcji, które mogą zostać
do tego celu wykorzystane. Pierwszą jest funkcja
time()
, zwracająca liczbę sekund
od 1 stycznia 1970 roku:
time_t time(time_t *timer);
Funkcja wypełnia przekazaną jej jako parametr zmienną typu
time_t
(
jeśli nie jest
to
NULL)
, zwracając tę samą wartość równieŜ jako wartość wyjściową. MoŜna więc
wywoływać funkcję
time()
z parametrem
NULL
i korzystać z wartości zwracanej:
#include <time.h>
void main()
{
time_t teraz;
teraz = time(NULL);
}
Funkcja
asctime()
zamienia strukturę
tm
na 26-znakowy ciąg (przedstawiony przy
opisie funkcji
ctime()
):
char *asctime(const struct tm *struktura);
Funkcja
ctime()
zamienia wartość czasu (zwracaną przez
time()
) na 26-znakowy
ciąg:
#include <stdio.h>
#include <time.h>
#include <string.h>
void main()
{
time_t teraz;
char data[30];
teraz = time(NULL);
strcpy(data,ctime(&teraz));
256
Hack Wars. Tom 1. Na tropie hakerów
256
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
}
Kolejna funkcja,
difftime()
, zwraca, liczoną w sekundach, róŜnicę między dwoma
wartościami typu
time_t
. SłuŜy więc do wyznaczania ilości czasu, jaki upłynął
między dwoma zdarzeniami, czasu wykonywania funkcji lub generowania przerw w
pracy programu, na przykład:
#include <stdio.h>
#include <time.h>
void DELAY(int okres)
{
time_t pocz;
pocz = time(NULL);
while(time(NULL) < pocz + okres)
;
}
void main()
{
printf("\nRozpoczynam oczekiwanie... (5 sekund)");
DELAY(5);
puts("\nOczekiwanie zako
ń
czone.");
}
Funkcja
gmtime()
zamienia lokalną wartość czasu
time_t
na wartość GMT o
postaci struktury
tm
. Działanie tej funkcji zaleŜy od ustawienia globalnej zmiennej
strefy czasowej. Struktura
tm
została wstępnie zdefiniowana w nagłówku
time.h
.
Przedstawiliśmy ją kilka stron wcześniej.
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
Element struktury
tm_mday
przechowuje dzień miesiąca (od 1 do 31), a
tm_wday
—
dzień tygodnia (gdzie niedzieli odpowiada 0). Czas jest mierzony od 1900 roku.
Wartość
tm_isdst
to znacznik, który informuje o tym, czy stosowany jest czas letni.
Stosowane nazwy struktury i jej elementów mogą róŜnić się w zaleŜności od
kompilatora, jednak sama struktura zasadniczo pozostaje niezmieniona.
Funkcja
mktime()
zamienia strukturę
tm
na wartość
time_t
, uzupełniając wartości
pól
tm_wday
i
tm_yday
:
time_t mktime(struct tm *t);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
257
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
257
W kolejnym przykładzie umoŜliwiamy wprowadzanie daty i uŜywamy funkcji
mktime()
do ustalenia dnia tygodnia. NaleŜy pamiętać, Ŝe funkcje związane z
czasem rozpoznają wyłącznie daty późniejsze niŜ 1 stycznia 1970:
#include <stdio.h>
#include <time.h>
#include <string.h>
void main()
{
struct tm tstruct;
int okay;
char data[100];
char *p;
char *wday[] =
{"niedziela","poniedziałek","wtorek","
ś
roda","czwartek","pi
ą
tek","sob
ota", "przed rokiem 1970 - nieznany"};
do
{
okay = 0;
printf("\nWprowad
ź
dat
ę
w formacie dd/mm/rr ");
p = fgets(data, 9,stdin);
p = strtok(data,"/");
if (p!= NULL)
tstruct.tm_mday = atoi(p);
else
continue;
p = strtok(NULL,"/");
if (p != NULL)
tstruct.tm_mon = atoi(p);
else
continue;
p = strtok(NULL, "/");
if (p != NULL)
tstruct.tm_year = atoi(p);
else
continue;
okay = 1;
}
while(!okay);
tstruct.tm_hour = 0;
tstruct.tm_min = 0;
tstruct.tm_sec = 1;
tstruct.tm_isdst = -1;
/* Teraz ustalimy dzie
ń
tygodnia */
if (mktime(&tstruct) == -1)
tstruct.tm_wday = 7;
printf ("Ten dzie
ń
to %s\n", wday[tstruct.tm_wday]);
}
Funkcja
mktime()
zapewnia równieŜ wprowadzenie odpowiednich poprawek dla
wartości przekraczających swój dopuszczalny zakres. MoŜna to wykorzystać do
ustalenia dokładnej daty, odległej o
n
dni:
258
Hack Wars. Tom 1. Na tropie hakerów
258
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
#include <stdio.h>
#include <time.h>
#include <string.h>
void main()
{
struct tm *tstruct;
time_t dzisiaj;
dzisiaj = time(NULL);
tstruct = localtime(&dzisiaj);
tstruct->tm_mday += 10;
mktime(tstruct);
if(tstruct->tm_year>99)
tstruct->tm_year%=100;
(te dwie linie zapobiegaj
ą
wy
ś
wietlaniu bł
ę
dnego roku w przypadku,
gdy czas, jaki upłyn
ą
ł od roku 1900 jest dłu
Ŝ
szy od 99 lat P.B.)
printf("Za dziesi
ęć
dni b
ę
dzie %02d/%02d/%02d\n",tstruct-
>tm_mday,tstruct->tm_mon +
1,tstruct->tm_year);
}
Pliki nagłówkowe
W plikach nagłówkowych umieszczone są prototypy funkcji bibliotecznych
kompilatora oraz standardowe makra. Norma ANSI wymienia następujące pliki tej
grupy.
Plik nagłówka
Opis
assert.h
definicja makra wspomagającego analizę programu,
assert
,
ctype.h
makra do klasyfikowania i konwersji znaków,
errno.h
stałe kodów błędów,
float.h
specyficzne dla implementacji makra dla operacji
zmiennoprzecinkowych,
limits.h
opisuje specyficzne dla implementacji ograniczenia dla wartości róŜnych
typów,
locale.h
parametry dla ustawień narodowych,
math.h
prototypy funkcji matematycznych,
setjmp.h
definicja
typedef
oraz funkcji dla
setjmp
i
longjmp
,
signal.h
zawiera stałe i deklaracje wykorzystywane przez funkcje
signal()
i
raise()
,
stdarg.h
zawiera makra do obsługi list argumentów,
stddef.h
definicje podstawowych typów danych i makr,
stdio.h
typy i makra wymagane do obsługi standardowych funkcji we-wy,
stdlib.h
zbiór róŜnorodnych, często wykorzystywanych deklaracji i prototypów
funkcji,
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
259
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
259
string.h
prototypy funkcji operujących na łańcuchach znakowych,
time.h
struktury dla procedur konwersji wartości daty i godziny.
Debugowanie programu
Norma ANSI definiuje wspomagające debugowanie makro
assert()
. Działa ono jak
instrukcja
if()
. Spełnienie podanego warunku powoduje jednak przerwanie
wykonywania programu i wyprowadzenie na standardowy strumień informacji o błędach
komunikatu:
Assertion failed: <test>, file < nazwa_pliku>, line <numer_wiersza>
Abnormal program termination
RozwaŜmy przykład, w którym program omyłkowo przypisuje wskaźnikowi wartość 0:
#include <stdio.h>
#include <assert.h>
void main()
{
/* Przykład zastosowania makra assert */
int *ptr;
int x;
x = 0;
/* W tym wierszu jest bł
ą
d! */
ptr=x;
assert(ptr != NULL);
}
Program taki po uruchomieniu zakończy szybko pracę wyświetleniem komunikatu:
Assertion failed: ptr != 0, file TEST.C, line 16
Abnormal program termination
Gdy program pracuje juŜ poprawnie, funkcja
assert()
moŜe zostać z niego usunięta
przez proste dopisanie przed wierszem
#include <assert.h>
:
#define NDEBUG
Dyrektywa taka zapewni oznaczenie, w przygotowywanym do kompilacji kodzie,
wszystkich funkcji
assert
jako komentarzy.
Błędy wartości zmiennoprzecinkowych
Liczby zmiennoprzecinkowe to ułamki dziesiętne, które nie odpowiadają dokładnie
ułamkom zwykłym (nie kaŜda liczba moŜe zostać równo podzielona przez 10).
Konsekwencją tego jest niebezpieczeństwo błędów zaokrągleń w obliczeniach.
PoniŜszy program przedstawia przykładowy problem:
#include <stdio.h>
260
Hack Wars. Tom 1. Na tropie hakerów
260
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
void main()
{
float liczba;
for(liczba = 1; liczba > 0.4; liczba -= 0.01)
printf ("\n%f",liczba);
}
Poczynając od wartości ok. 0,47 (zaleŜnie od komputera i kompilatora), program
zacznie generować niedokładne wartości.
Zjawisko to moŜna ograniczyć, stosując dłuŜsze typy zmiennoprzecinkowe,
double
lub
long double
, korzystające z większej liczby bitów. Gdy wymagana jest wysoka
dokładność,
naleŜy
uŜywać
wartości
całkowitych,
zamienianych
na
zmiennoprzecinkowe tylko przed wyświetleniem na ekranie. Nie wolno równieŜ
zapominać, Ŝe większość kompilatorów C jako domyślny typ zmiennoprzecinkowy
przyjmuje
double
i uŜycie innych typów moŜe wymagać konwersji.
Obsługa błędów
Gdy w trakcie wykonywania programu wystąpi błąd systemowy — jak na przykład
przy nieudanej próbie otwarcia pliku — powinien zostać wyświetlony komunikat
dostarczający informacji o zdarzeniu. Takie przygotowanie pomaga w duŜej mierze
programiście, dostarczając mu, jeŜeli nie informacji o przyczynie błędu, to
przynamniej pewnych wskazówek. Norma C opisuje funkcję obsługującą wymianę
tego rodzaju informacji,
perror()
:
void perror(const char *s);
Jako parametr podajemy ciąg, który funkcja wyświetli jako pierwszy. Drugim
ciągiem, wyświetlanym po dwukropku, jest treść systemowego komunikatu błędu.
PoniŜej przedstawiamy prosty przykład uŜycia funkcji
perror()
:
#include <stdio.h>
void main()
{
FILE *fp;
char nazwa[] = "nic.xyz";
fp = fopen(nazwa,"r");
if(!fp)
perror(nazwa);
return;
}
JeŜeli operacja
fopen()
nie zostanie wykonana, wyświetlany jest komunikat w
rodzaju:
nic.xyz: No such file or directory
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
261
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
261
Funkcja
perror()
przesyła dane do strumienia predefiniowanego
stderr
,
zazwyczaj skojarzonego z kartą graficzną i monitorem komputera. Komunikat
systemowy odnajdywany jest za pośrednictwem zmiennej globalnej
errno
, której
wartość ustawia większość funkcji systemowych (choć nie wszystkie).
Jedynie w wyjątkowych przypadkach skorzystać moŜna z funkcji
abort()
, która,
przerywając wykonywanie programu, wyświetla komunikat w rodzaju „Niewłaściwe
zakończenie programu” i zwraca procesowi nadrzędnemu lub systemowi
operacyjnemu kod 3.
Obsługa błędów krytycznych na platformie IBM PC i w systemie DOS
System PC DOS przewiduje moŜliwość wpływu uŜytkownika na działania
podejmowane przez funkcje obsługi błędów krytycznych. Łatwo się o tym przekonać,
próbując dokonać zapisu w pustej stacji dyskietek. Pojawia się wówczas znajome:
Not ready; error writing drive A
Abort Retry Ignore?
PoniŜszy program przedstawia, w jaki sposób moŜna przekierować obsługę tego
rodzaju zdarzeń do własnych funkcji:
#include <stdio.h>
#include <dos.h>
void interrupt nowe_int();
void interrupt (*stare_int)();
char stan;
void main()
{
FILE *fp;
stare_int = getvect(0x24);
/* Skieruj obsług
ę
bł
ę
dów krytycznych do mojej funkcji */
setvect(0x24, nowe_int);
/* Generuj bł
ą
d braku dysku w stacji A */
fp = fopen("a:\\dane.txt","w+");
/* Wy
ś
wietl kod stanu */
printf("\nKod stanu == &d",stan);
}
void interrupt nowe_int()
{
/* zapisz warto
ść
globalnego kodu bł
ę
du */
stan = _DI;
/* ignoruj bł
ą
d i zako
ń
cz */
_AL = 0;
}
262
Hack Wars. Tom 1. Na tropie hakerów
262
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Gdy wywoływane jest przerwanie błędu krytycznego systemu DOS, komunikat stanu
umieszczany jest w młodszym bajcie rejestru DI. Lista kodów obejmuje wymienione
poniŜej.
Kod stanu operacji
Znaczenie
00
zabezpieczenie przed zapisem,
01
jednostka nieznana,
02
dysk nie jest gotowy,
03
nieznane polecenie,
04
błąd danych, niezgodna wartość CRC,
05
niewłaściwa długość struktury Ŝądania,
06
błąd wyszukiwania,
07
nieznany nośnik danych,
08
nie znaleziono sektora,
09
brak papieru w drukarce,
0A
błąd zapisu,
0B
błąd odczytu,
0C
błąd ogólny.
Własna funkcja obsługi błędów krytycznych moŜe przekazać komunikat stanu do
zmiennej globalnej i ustawić w rejestrze AL kod dalszych czynności.
Kod
Operacja
00
ignoruj błąd,
01
powtórz,
02
zakończ program,
03
zrezygnuj (dostępny od DOS 3.3).
JeŜeli decydujemy się na ustawienie wartości rejestru AL na 02, musimy koniecznie
zadbać o zamknięcie wszystkich plików — DOS kończy pracę programu natychmiast,
pozostawiając otwarte pliki i poprzydzielane bloki pamięci.
PoniŜej przedstawiamy całkiem praktyczną funkcję słuŜącą do sprawdzania, czy moŜna
uzyskać dostęp do określonego dysku. Ma ona współpracować z przedstawionym
wcześniej programem obsługi błędów i zmienną globalną
stan
:
int DISKOK(int dysk)
{
/* Sprawdza czy mo
Ŝ
na odczytywa
ć
dane z okre
ś
lonego dysku */
/* Zwraca false (0) w przypadku bł
ę
du */
/* St
ą
d if(!DISKOK(dysk)) */
/* error(); */
unsigned char bufor[25];
/* Zakładamy,
Ŝ
e wszystko jest OK */
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
263
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
263
stan = 0;
/* Je
Ŝ
eli znamy ju
Ŝ
dysk, zwracamy OK */
if ('A' + dysk == diry[0])
return(1);
/* Próbuj odczytu z dysku */
memset(bufor,0,20);
sprintf(bufor,"%c:$$$.$$$",'A'+dysk);
_open(bufor,O_RDONLY);
/* Sprawd
ź
stan */
if (stan == 0)
return(1);
/* Odczyt nie jest mo
Ŝ
liwy */
return(0);
}
Konwersja typów zmiennych
Konwersja typów zmiennych pozwala poinformować kompilator, jakiego typu są
wykorzystywane dane oraz zmieniać ich typ. RozwaŜmy następujący przykład:
#include <stdio.h>
void main()
{
int x;
int y;
x = 10;
y = 3;
printf("\n%1f", x / y);
}
Poinformowaliśmy funkcję
printf()
, Ŝe powinna oczekiwać wartości
double
.
Kompilator rozpoznaje jednak liczby całkowite
x
i
y
— generowany jest błąd (tylko
w niektórych środowiskach programistycznych — np. Microsoft Visual C++ bez
problemu skompiluje taki program, tyle, Ŝe wyświetlona wartość będzie równa 0 P.B.).
Aby program mógł działać, naleŜy poinformować kompilator, Ŝe wartość wyraŜenia
x/y
będzie miała typ
double
:
#include <stdio.h>
void main()
{
int x;
int y;
x = 10;
y = 3;
printf("\n%1f", (double)(x / y));
}
264
Hack Wars. Tom 1. Na tropie hakerów
264
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Zwróćmy uwagę na fakt, Ŝe w nawiasy ujęta jest zarówno nazwa typu, jak i samo
konwertowane wyraŜenie. Kompilator traktuje teraz liczby
x
i
y
jako całkowite, a
wynik operacji dzielenia jako wartość
double
— oznacza to, Ŝe przeprowadzone
zostanie dzielenie całkowite (dające w tym wypadku wynik równy 3). Rozwiązaniem
jest zatem konwersja obu zmiennych:
#include <stdio.h>
void main()
{
int x;
int y;
x = 10;
y = 3;
printf("\n%1f", (double)(x) / (double)(y));
}
Teraz, gdy obie liczby mają typ
double
, oczywiste jest, Ŝe i wynik ich dzielenia
będzie wartością
double
.
Prototypy
Zadaniem prototypów funkcji jest odpowiednio wczesne dostarczenie kompilatorowi
informacji o tym, jakiego typu wartości funkcja pobiera i zwraca. Przyjrzyjmy się na
przykład funkcji
strtok()
o prototypie:
char *strtok(char *s1, const char *s2);
Jest to informacja dla kompilatora, Ŝe funkcja
strtok
zwraca wskaźnik do danych
znakowych. Pierwszym parametrem będzie wskaźnik do ciągu znakowego i parametr
ten jest modyfikowalny wewnątrz funkcji. Drugi parametr to wskaźnik do ciągu
znakowego, którego zawartość nie moŜe być przez funkcję zmieniana. Kompilator
dysponuje dzięki temu informacją o ilości pamięci wymaganej przez wartość funkcji
sizeof (char *)
. Bez dostępu do prototypu funkcji kompilator przyjąłby
załoŜenie, Ŝe funkcja zwraca wartość
int
i zaalokował pamięć wielkości
(sizeof(int))
. JeŜeli wartość
int
i wskaźnik korzystają na danym komputerze z
takiej samej liczby bajtów, nic złego się nie dzieje. Jednak jeŜeli rozmiar wskaźnika
jest większy, kompilator nie będzie dysponował odpowiednią ilością miejsca dla
wartości zwróconej przez funkcję
strtok()
. Wykorzystywany jest wówczas
nieoczekiwanie kolejny adres pamięci.
Szczęśliwie jednak większość kompilatorów C ostrzega o niepoprzedzonych
prototypem wywołaniach funkcji. Oto prosty przykład programu, na którego
kompilację nie pozwoli większość nowoczesnych kompilatorów C.
#include <stdio.h>
int funkcjaa(int x, int y)
{
return(MNOZ(x,y));
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
265
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
265
double MNOZ(double x, double y)
{
return(x * y);
}
void main()
{
printf("\n%d",funkcjaa(5,5));
}
Gdy kompilator po raz pierwszy napotyka funkcję
MNOZ()
, jest to wywołanie z
wnętrza bloku
funkcjaa()
. W przypadku braku prototypu
MNOZ()
przyjmowane
jest załoŜenie, Ŝe funkcja zwraca wartość
int
. Kiedy kompilator napotyka definicję
funkcji
MNOZ()
, stwierdza, Ŝe deklarowany jest typ
double
. Generowany jest
wówczas błąd kompilacji:
"Type mismatch in redeclaration of function 'MNOZ'"
Jest to wyraźny nakaz wprowadzenia prototypu funkcji! Gdyby udało się taki
program skompilować i uruchomić, najbardziej prawdopodobnym scenariuszem
byłaby awaria stosu.
Wskaźniki do funkcji
Język C przewiduje, Ŝe wskaźnik moŜe odwoływać się do adresu funkcji. Co więcej,
wskaźnik taki moŜe być stosowany w miejsce jawnego wywołania funkcji.
Wykorzystują to funkcje modyfikujące przerwania. Mechanizm ten moŜna równieŜ
zastosować do indeksowania funkcji, na przykład:
#include <stdio.h>
#include <math.h>
double (*fp[7])(double x);
void main()
{
double x;
int p;
fp[0] = sin;
fp[1] = cos;
fp[2] = acos;
fp[3] = asin;
fp[4] = tan;
fp[5] = atan;
fp[6] = ceil;
p = 4;
x = fp[p](1.5);
printf("\nWynik: %1f",x);
}
Definiujemy tu tablicę wskaźników do funkcji
(*fp[])()
, które wywoływane są
odpowiednio do wartości zmiennej
p
. Identycznie działa program:
266
Hack Wars. Tom 1. Na tropie hakerów
266
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
#include <stdio.h>
#include <math.h>
void main()
{
double x;
int p;
p = 4;
switch (p)
{
case 0 : x = sin(1.5);
break;
case 1 : x = cos(1.5);
break;
case 2 : x = acos(1.5);
break;
case 3 : x = asin(1.5);
break;
case 4 : x = tan(1.5);
break;
case 5 : x = atan(1.5);
break;
case 6 : x = ceil(1.5);
break;
}
printf("\nWynik: %1f",x);
}
Dzięki zastosowaniu wskaźników do funkcji kod w pierwszym przykładzie jest
zwięźlejszy i szybciej wykonywany. Tablica wskaźników do funkcji moŜe być
przydatnym narzędziem podczas pisania interpretera języka. Program porównuje
wówczas wprowadzoną instrukcję z tablicą słów kluczowych, wyszukując
odpowiednią wartość indeksującą. Następnie wywoływany jest odpowiedni wskaźnik
do funkcji. Rozwiązanie takie znakomicie zastępuje rozbudowaną instrukcję
switch()
.
Sizeof
Instrukcja preprocesora
sizeof
zwraca rozmiar elementu danych, którym moŜe być
struktura, wskaźnik, ciąg lub dowolny inny obiekt. Jednak i w tym przypadku
wymagana jest odrobina uwagi. Przyjrzyjmy się programowi:
#include <stdio.h>
#include <mem.h>
char ciag1[80]; char *tekst = "To jest ciag znakowy";
void main()
{
/* Inicjujemy ciag1 */
memset(ciag1, 0, sizeof(ciag1));
/* Kopiujemy do ciagu pewien tekst ? */
memcpy(ciag1, tekst, sizeof(tekst));
/* Wy
ś
wietlamy ciag1 */
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
267
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
267
printf("\nCi
ą
g 1 = %s\n",ciag1);
}
Rozpoczynamy od zainicjowania ciągu
ciag1
80 zerami, po czym kopiujemy ciąg
tekst
do zmiennej
ciag1
. Jednak zmienna
tekst
jest wskaźnikiem, a więc
sizeof(tekst)
zwraca rozmiar wskaźnika (najczęściej 2 bajty), a nie długość ciągu,
który wskazuje. Błąd taki moŜe pozostać niezauwaŜony, gdy długość ciągu jest zgodna
z rozmiarem wskaźnika.
Przerwania
BIOS komputera PC i system DOS wyposaŜone są w funkcje, które mogą być
wywoływane przez program za pośrednictwem skojarzonego z nimi numeru
przerwania.
Adresy
przypisanych
do
poszczególnych
przerwań
funkcji
przechowywane są w tabeli w pamięci RAM, określanej jako tablica wektorów
przerwań. Zmiana adresu wektora przerwania umoŜliwia programowi zastąpienie
standardowej funkcji obsługi przerwania funkcją własną.
Borland Turbo C oferuje dwie funkcje biblioteczne słuŜące do odczytywania i
modyfikowania wartości wektorów przerwań:
setvect()
i
getvect()
. Ich
odpowiedniki w bibliotekach Microsoftu noszą nazwy:
_dos_setvect()
i
_dos_getvect()
.
Prototyp funkcji
getvect()
ma postać:
void interrupt(*getvect(int nr_przerwania))();
a prototyp funkcji
setvect()
:
void setvect(int nr_przerwania, void interrupt(*funkcja)());
Przy odczytywaniu i zapisywaniu adresów uŜycie funkcji
getvect()
wygląda
następująco:
void interrupt(*stary)(void);
void main()
{
/* pobierz stary wektor przerwania */
stary = getvect(0x1C);
.
.
.
}
W tym przypadku pobieranym wektorem jest 0x1C. Aby ustawić wektor na adres
własnej funkcji, uŜywamy
setvect()
:
void interrupt nowa(void)
{
.
.
/* Nowa funkcja obsługi przerwania */
.
.
268
Hack Wars. Tom 1. Na tropie hakerów
268
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
.
}
void main()
{
.
.
.
setvect(0x1C, nowa);
.
.
.
.
}
Gdy zajmujemy się przerwaniami, nie moŜemy zapominać o dwóch rzeczach.
Pierwszą jest to, Ŝe przed zmianą wektora przerwania wywoływanego zdarzeniami
zewnętrznymi naleŜy wstrzymać moŜliwość takich wywołań poleceniem
disable()
.
Po wprowadzeniu modyfikacji przywracamy funkcjonowanie przerwania poleceniem
enable()
. Efekt wywołania przerwania w trakcie zmiany wektora (gdy nie uŜyjemy
funkcji
disable()
) jest nieprzewidywalny.
Drugą istotną rzeczą jest zadbanie o przywrócenie stanu wektorów przerwań przed
zakończeniem pracy programu i przywróceniem kontroli systemowi operacyjnemu.
Wyjątkiem jest jedynie wektor przerwania obsługi błędów krytycznych, przywracany
przez system DOS automatycznie.
PoniŜszy przykładowy program przejmuje przerwanie zegara systemowego (hooks an
interrupt):
#include <stdio.h>
#include <dos.h>
#include <time.h>
#include <conio.h>
#include <stdlib.h>
enum {FALSE, TRUE};
#define KOLOR (NIEBIESKI << 4 ) | ZOLTY
#define BIOS_TIMER 0x1C
static unsigned zainstalowany = FALSE;
static void interrupt (*stary_tick) (void);
static void interrupt tick (void)
{
int i;
struct tm *teraz;
time_t godzina;
char bufor[9];
static time_t godzina_pop = 0L;
static char bufor_wysw[20] =
{
' ', KOLOR, '0', KOLOR, '0', KOLOR, ':', KOLOR, '0', KOLOR,
'0', KOLOR, ':', KOLOR, '0', KOLOR, '0', KOLOR, ' ', KOLOR
};
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
269
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
269
enable ();
if (time (&godzina) != godzina_pop)
{
godzina_pop = godzina;
teraz = localtime(&godzina);
sprintf(bufor,"02d:%02d.%02d",teraz->tm_hour,teraz->tm_min,teraz-
>tm_sec);
for (i = 0; i < 8; i++)
{
bufor_wysw[(i + 1) << 1] = bufor[i];
}
puttext (71, 1, 80, 1, bufor_wysw);
}
stary_tick();
}
void zatrzymaj (void)
{
if (zainstalowany)
{
setvect(BIOS_TIMER, stary_tick);
zainstalowany = FALSE;
}
}
void zegar_start (void)
{
static unsigned pierwszy_raz = TRUE;
if (!zainstalowany)
{
if (pierwszy_raz);
{
atexit (zatrzymaj);
pierwszy_raz = FALSE;
}
stary_tick = getvect (BIOS_TIMER);
setvect (BIOS_TIMER, tick);
zainstalowany = TRUE;
}
}
Funkcja signal()
Przerwania moŜna przechwytywać i wykorzystywać na róŜne sposoby. Jednym z
prostszych jest uŜycie funkcji
signal()
. Korzysta ona z dwóch parametrów:
void (*signal (int sygnał, void(*funkcja) (int))) (int);
Pierwszy parametr określa przechwytywany sygnał, drugi — wywoływaną w
momencie uaktywnienia sygnalizatora funkcję. Większość sygnałów zdefiniowanych
270
Hack Wars. Tom 1. Na tropie hakerów
270
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
zostało w nagłówku signal.h. W pliku tym umieszczonych zostało równieŜ kilka makr
— wykonujących podstawowe operacje, takie jak ignorowanie sygnału — mogą one
zostać uŜyte jako parametr
sygnał
.
Na platformie PC często pojawia się potrzeba uniemoŜliwienia korzystania z
przerywającej pracę programu kombinacji klawiszy CTRL+BREAK. PoniŜsze
wywołanie funkcji
signal()
zastępuje standardową obsługę predefiniowanego
sygnału
SIGINT
(obsługującego Ŝądanie CTRL+BREAK) równieŜ predefiniowanym
makrem
SIG-IGN
, co prowadzi do ignorowania wciskanych przez uŜytkownika
klawiszy.
signal(SIGINT,SIG_IGN);
W poniŜszym przykładzie instalujemy przechwytywanie błędów operacji
zmiennoprzecinkowych i dzielenia przez 0 (na komputerze klasy PC):
#include <stdio.h>
#include <signal.h>
void (*stary_sygnal)(int);
void przechwyc(int sygnal)
{
printf("Funkcja przechwyc wywołana przez: %d\n",sygnal);
}
void main()
{
int a;
int b;
stary_sygnal = signal(SIGFPE,przechwyc);
a = 0;
b = 10 / a;
/* Przed wy
ś
ciem przywracamy obsług
ę
standardow
ą
! */
signal(SIGFPE, stary_sygnal);
}
Dynamiczne alokowanie pamięci
JeŜeli program wymaga pewnej tabeli, której rozmiar ulega zmianom (na przykład do
przechowywania listy plików katalogu bieŜącego), deklarowanie jej rozmiaru jako
największego
dopuszczalnego
czy
wymaganego,
nie
jest
ekonomicznym
rozwiązaniem. Istnieje bowiem moŜliwość dynamicznego alokowania pamięci w
zaleŜności od pojawiających się wymagań.
Pamięć dostępną do alokacji dynamicznej Turbo C przyznaje w obszarze nazywanym
stertą (heap). Rozmiar sterty zaleŜy od przyjętego modelu pamięci. Model tiny
przewiduje, Ŝe zajętych zostanie ogółem nie więcej niŜ 64 kB. Model small przyznaje
64 kB dla kodu programu i sterty, umoŜliwiając korzystanie ze sterty odległej, która
moŜe zajmować pozostałą pamięć konwencjonalną. Inne modele pamięci
udostępniają stercie całą pamięć konwencjonalną. Przy korzystaniu z pierwszego
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
271
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
271
wymienionych modeli, tj. tiny, istotne jest ograniczenie zuŜycia pamięci. Często
redukuje się wówczas rozmiar sterty do minimum (czyli jednego bajtu).
Język C oferuje funkcję
malloc()
, pozwalającą zaalokować wolny blok pamięci o
określonym rozmiarze. Funkcja zwraca wskaźnik do początku bloku. Dostępna jest
równieŜ funkcja
free()
, dealokująca blok przyznany wcześniej poleceniem
malloc()
. Podczas korzystania z nich nie wolno zapominać, Ŝe komputery PC nie
zwalniają poprawnie bloków pamięci i wielokrotne uŜycie obu funkcji prowadzi do
fragmentacji dostępnych zasobów, a w konsekwencji — niekiedy — braku pamięci.
PoniŜszy program wyszukuje we wskazanym pliku określony ciąg znaków
(rozróŜniając wielkość liter). Funkcja
malloc()
wykorzystywana jest do zaalokowania
dokładnie takiej ilości pamięci, jaka jest wymagana, aby załadować do niej zawartość
pliku:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *bufor;
void main(int argc, char *argv[])
{
FILE *fp;
long flen;
/* Sprawd
ź
liczb
ę
parametrów */
if (argc != 3)
{
fputs("Prawidłowa składnia wywołania: sgrep <tekst>
<plik>",stderr);
exit(0);
}
/* Otwórz powi
ą
zany z plikiem strumie
ń
fp */
fp = fopen(argv[2],"r");
if (!fp)
{
perror("Nie mo
Ŝ
na otworzy
ć
pliku");
exit(0);
}
/* Znajd
ź
koniec pliku */
if (fseek(fp,0L,SEEK_END))
{
fputs("Nie mo
Ŝ
na okre
ś
li
ć
długo
ś
ci pliku", stderr);
fclose(fp);
exit(0);
}
/* Okre
ś
l długo
ść
pliku */
flen = ftell(fp);
/* Sprawd
ź
czy OK. */
if (flen == -1L)
{
fputs("Nie mo
Ŝ
na okre
ś
li
ć
długo
ś
ci pliku", stderr);
272
Hack Wars. Tom 1. Na tropie hakerów
272
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
fclose(fp);
exit(0);
}
/* Ustaw wska
ź
nik na pocz
ą
tek pliku */
rewind(fp);
/* Zaalokuj bufor pami
ę
ci */
bufor = malloc(flen);
if (!bufor)
{
fputs("Nieudana alokacja pami
ę
ci", stderr);
fclose(fp);
exit(0);
}
/* Załaduj plik do bufora */
fread(bufor,flen,1,fp);
/* Sprawd
ź
czy odczyt udany */
if(ferror(fp))
{
fputs("Bł
ą
d odczytu pliku",stderr);
/* Dealokacja bloku pami
ę
ci */
free(bufor);
fclose(fp);
exit(0);
}
printf("%s %s w pliku %s",argv[1],(strstr(bufor,argv[1])) ?
"znalezione" :
"nieznalezione",argv[2]);
/* Przed wyj
ś
ciem dealokujemy blok pami
ę
ci */
free(bufor);
fclose(fp);
}
Funkcja atexit()
Gdy program kończy pracę, powinien zamknąć wszystkie otwarte pliki (wyręcza w tym
programistę kod rozpoczęcia i zakończenia programu, dołączany przez kompilator C)
oraz przywrócić komputer do stanu pewnego uporządkowania. W przypadku duŜego
programu, z którego wyjście moŜe nastąpić w wielu róŜnych punktach, wielokrotne
wypisywanie wywołań do procedury oczyszczającej wymaga pewnego wysiłku. Nie
jest on, na szczęście, konieczny.
Norma ANSI opisuje funkcję
atexit()
, która rejestruje określoną funkcję,
przekazaną poprzez parametr, jako funkcję, która zostanie wywołana przy kończeniu
programu. Zapewniamy w ten sposób jej automatyczne wywołanie. PoniŜszy
program, bez względu na to, czy wystąpił w nim błąd, czy nie, wywołuje funkcję
zakoncz()
.
#include <stdio.h>
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
273
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
273
#include <stdlib.h>
void zakoncz()
{
puts("\nPrzetwarzanie zako
ń
czone.");
}
void main()
{
FILE *fp;
int a;
int b;
int c;
int d;
int e;
char tekst[100];
atexit(zakoncz);
fp = fopen("dane.txt","w");
if (!fp)
{
perror("Nie mo
Ŝ
na utworzy
ć
pliku");
exit(0);
}
fprintf(fp,"1 2 3 4 5 \"Wiersz liczb\"");
fflush(fp);
if (ferror(fp))
{
fputs("Bł
ą
d przy zapisywaniu danych strumienia",stderr);
exit(1);
}
rewind(fp);
if (ferror(fp))
{
fputs("Bł
ą
d zerowania wska
ź
nika strumienia",stderr);
exit(1);
}
fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,tekst);
if (ferror(fp))
{
/* Je
Ŝ
eli nie zauwa
Ŝ
yłe
ś
wcze
ś
niejszego bł
ę
du */
/* program ko
ń
czy prac
ę
w tym miejscu */
fputs("Bł
ą
d przy odczycie strumienia",stderr);
exit(1);
}
printf("\nFunkcja fscanf() zwróciła %d %d %d %d %d
%s",a,b,c,d,e,tekst);
}
274
Hack Wars. Tom 1. Na tropie hakerów
274
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
Wydajność
Gdy pojawia się kwestia skrócenia czasu wykonywania programu, istotna jest
znajomość komputera, na którym ten będzie uruchamiany. Większość systemów
stosunkowo wolno wyświetla informacje na ekranie. Język C oferuje róŜnorodne
funkcje, słuŜące do tego celu. Najbardziej typową, ale zarazem najwolniejszą, jest
printf()
. Gdy tylko jest to moŜliwe, warto korzystać z konstrukcji
puts(nazwa_zmiennej)
w miejsce
printf("%s\n",nazwa_zmiennej)
(
puts()
dołącza do przesyłanego na ekran ciągu znak nowego wiersza automatycznie).
Podczas mnoŜenia zmiennej przez stałą o wartości 2 wiele kompilatorów C rozpoznaje,
Ŝ
e w kodzie asemblera jedyną wymaganą operacją jest przesunięcie bitowe w lewo.
Przy mnoŜeniu przez inne wartości często szybszą pracę zapewni zastosowanie
dodawania, a więc zamiast:
x * 3
piszemy:
x + x + x
Jest to korzystne, gdy mnoŜnikiem jest stała. Nie naleŜy jednak stosować takiej
konstrukcji z róŜnymi wartościami mnoŜnika w pętli.
Kolejną metodą przyspieszenia operacji mnoŜenia i dzielenia jest odwołanie się do
funkcji przesunięcia bitowego,
<<
i
>>
. Instrukcję
x/=2
moŜna zapisać jako
x>>=1
(przesunięcie bitowe o jedną pozycję w prawo). Wiele kompilatorów samodzielnie
zamienia operację dzielenia przez dwa na operację przesunięcia w prawo.
Przesunięcia moŜna jednak stosować równieŜ przy mnoŜeniu i dzieleniu przez 2, 4, 8,
16, 32, 64, 128, 256, 512, 1024 itd. JeŜeli polecenia przesunięcia są Ci obce,
wystarczy rozwaŜenie dwójkowej postaci liczby:
01001101
czyli, dziesiętnie, 77. Przesunięcie w prawo prowadzi do wartości:
00100110
czyli, dziesiętnie, 38.
Warto stosować w miejsce liczb zmiennoprzecinkowych wartości całkowite. Jest to
moŜliwe w większej ilości sytuacji, niŜ mogłoby się początkowo wydawać.
Przykładowo do przekształcenia ułamka na liczbę dziesiętną normalnie uŜywamy
polecenia:
procent = x / y * 100;
Wymaga to zastosowania zmiennych zmiennoprzecinkowych. MoŜna jednak
posłuzyć się konstrukcją:
z = x * 100;
procent = z / y;
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
275
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
275
Przeszukiwanie katalogów
Funkcje „znajdź pierwszy” i „znajdź następny” stosuje się do wyszukiwania w
katalogu systemu DOS określonej nazwy lub nazw plików. Pierwsza z nich, „znajdź
pierwszy”, realizowana jest za pośrednictwem przerwania systemu DOS nr 21,
funkcji 4E. Pobiera ona specyfikację nazwy pliku w postaci ciągu ASCII, który moŜe
zawierać symbole wieloznaczne, oraz wymagane atrybuty pliku. Funkcja umieszcza
informacje o pliku w obszarze przesyłania danych dysku (DTA, disk transfer area)
i zwraca wyłączony znacznik przeniesienia (carry flag). W przypadku wystąpienia
błędu, którego przyczyną moŜe być brak plików odpowiadających podanemu
wzorcowi, funkcja zwraca ustawiony znacznik przeniesienia.
Po udanym wywołaniu funkcji „znajdź pierwszy” program moŜe wywołać funkcję
„znajdź następny” (przerwanie systemu DOS nr 21, funkcja 4F), która wyszuka kolejny
plik odpowiadający specyfikacji podanej w wywołaniu „znajdź pierwszy”. Udane
wykonanie kończy umieszczenie informacji o pliku w obszarze DTA i zwrócenie
wyłączonego znacznika przeniesienia. W pozostałych przypadkach znacznik jest
ustawiany.
Większość kompilatorów C dla komputerów PC oferuje niestandardowe funkcje
biblioteczne, umoŜliwiające korzystanie z tych dwóch funkcji systemu DOS. W
Turbo C noszą one nazwy
findfirst()
i
findnext()
(wykorzystanie funkcji
bibliotecznych uwalnia programistę od niezbyt wygodnego, bezpośredniego
korzystania z DTA). JeŜeli korzystamy z Microsoft C, zamiast
findfirst()
i
findnext()
uŜywamy
_dos_ findfirst()
i
_dos_findnext()
.
PoniŜszy przykład jest próbą najprostszej implementacji polecenia systemu DOS,
dir
:
#include <stdio.h>
#include <dir.h>
#include <dos.h>
void main(void)
{
/* Wy
ś
wietlanie zawarto
ś
ci katalogu bie
Ŝą
cego */
int skonczone;
int dz;
int mies;
int rok;
int godz;
int min;
char amflag;
struct ffblk ffblk;
struct fcb fcb;
/* Rozpocznij od wy
ś
wietlenia podkatalogów */
skonczone = findfirst("*.",&ffblk,16);
while (!skonczone)
{
rok = (ffblk.ff_fdate >> 9) + 80;
mies = (ffblk.ff_fdate >> 5) & 0x0f;
dz = ffblk.ff_fdate & 0x1f;
276
Hack Wars. Tom 1. Na tropie hakerów
276
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
godz = (ffblk.ff_ftime >> 11);
min = (ffblk.ff_ftime >> 5) & 63;
amflag = 'a';
if (godz > 12)
{
godz -= 12;
amflag = 'p';
}
printf("&-11.11s <DIR> %02d-%02d-%02d %2d:%02d%c\n",
ffblk.ff_name,dz,mies,rok,godz,min,amflag);
skonczone = findnext(&ffblk);
}
/* Teraz wszystkie pliki, z wyj
ą
tkiem katalogów */
skonczone = findfirst("*.*",&ffblk,231);
while (!skonczone)
{
rok = (ffblk.ff_fdate >> 9) + 80;
mies = (ffblk.ff_fdate >> 5) & 0x0f;
dz = ffblk.ff_fdate & 0x1f;
godz = (ffblk.ff_ftime >> 11);
min = (ffblk.ff_ftime >> 5) & 63;
amflag = 'a';
if (godz > 12)
{
godz -= 12;
amflag = 'p';
}
parsfnm(ffblk.ff_name,&fcb,1);
printf("%-8.8s %-3.3s %8ld %02d-%02d-%02d %2d:%02d%c\n",
fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize,
dz,mies,rok,godz,min,amflag);
skonczone = findnext(&ffblk);
}
}
Funkcja
parsfnm()
to polecenie biblioteki Turbo C, wykorzystujące funkcję
systemu DOS do rozdzielenia zawierającego nazwę pliku ciągu ASCII na części
składowe. Są one umieszczane w tzw. bloku kontrolnym pliku (FCB, file control
block), skąd mogą zostać łatwo pobrane i wyświetlone przez
printf()
. Obszar DTA
systemu DOS opiera się na następującej strukturze.
Przesunięcie
Długość
Zawartość
00
15
zarezerwowane,
15
Bajt
atrybuty pliku,
16
Słowo
godzina,
18
Słowo
data,
1A
04
rozmiar,
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
277
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
277
1E
0D
nazwa i rozszerzenie pliku jako ciąg ASCII.
Pole godziny odnosi się do ostatniej modyfikacji pliku i zawiera następujące elementy.
Bity
Zawartość
0 – 4
liczba sekund podzielona przez 2,
5 – 10
minuty,
11 – 15
godziny.
Pole daty równieŜ odnosi się do ostatniej modyfikacji pliku.
Bity
Zawartość
0 – 4
dzień,
5 – 8
miesiąc,
9 – 15
liczba lat od roku 1980.
Pobranie tych danych z obszaru DTA wymaga nieco wysiłku, co widać na
wcześniejszym przykładzie. Szerszego komentarza wymaga jeszcze bajt atrybutów.
Bity
Zawartość
0
tylko do odczytu,
1
ukryty,
2
systemowy,
3
etykieta woluminu,
4
katalog,
5
archiwum
Dostęp do pamięci rozbudowanej
Pamięć RAM komputerów PC dostępna jest w trzech odmianach: jako pamięć
konwencjonalna (conventional), rozbudowana (expanded) i rozszerzona (extended).
Pamięć konwencjonalna to początkowe 640 kB, dostępne dla systemu operacyjnego
DOS. Jest to pamięć wykorzystywana standardowo, choć często niewystarczająca.
Adresy pamięci rozbudowanej pozostają poza zakresem pamięci konwencjonalnej.
Dostępu do niej nie umoŜliwia sam system DOS, ale dodatkowy program, tzw.
program obsługi pamięci LIM EMS. Do jego wywoływania słuŜy przerwanie 67h.
Głównym problemem w dostępie do pamięci rozbudowanej jest to, Ŝe bez względu na
ilość tej pamięci w komputerze, jej adresowanie odbywa się za pośrednictwem 16-ki-
lobajtowych bloków, tzw. stron. JeŜeli więc programowi przyznane zostaną 2 MB
pamięci rozbudowanej, będzie to 128 stron (128
∗
16 kB = 2 MB). Dostępność
sterownika LIM EMS moŜna ustalić, otwierając plik (urządzenie IOCTL)
278
Hack Wars. Tom 1. Na tropie hakerów
278
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
EMMXXXX0. Norma LIM wymaga bowiem, aby plik taki istniał, gdy sterownik jest
aktywny.
PoniŜszy przykład ilustruje sposób korzystania z podstawowych funkcji kontroli i
dostępu do pamięci rozbudowanej.
#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define EMM 0x67
char far *emmbase;
emmtest()
{
/*
Sprawdza czy istnieje pami
ęć
rozbudowana,
próbuj
ą
c otworzy
ć
plik EMMXXXX0.
*/
union REGS regs;
struct SREGS sregs;
int error;
long handle;
/* Próba otwarcia urz
ą
dzenia plikowego EMMXXXX0 */
regs.x.ax = 0x3d00;
regs.x.dx = (int)"EMMXXXX0";
sregs.ds = _DS;
intdosx(®s,®s,&sregs);
handle = regs.x.ax;
error = regs.x.cflag;
if (!error)
{
regs.h.ah = 0x3e;
regs.x.bx = handle;
intdos(®s,®s);
}
return error;
}
emmok()
{
/*
Sprawdza funkcjonowanie mened
Ŝ
era pami
ę
ci robudowanej
*/
union REGS regs;
regs.h.ah = 0x40;
int86(EMM,®s,®s);
if (regs.h.ah)
return 0;
regs.h.ah = 0x41;
int86(EMM,®s,®s);
emmbase = MK_FP(regs.x.bx,0);
return 1;
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
279
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
279
}
long emmavail()
{
/*
Zwraca liczb
ę
dost
ę
pnych (wolnych) 16-kilobajtowych stron
pami
ę
ci rozbudowanej lub -1 w przypadku bł
ę
du.
*/
union REGS regs;
regs.h.ah = 0x42;
int86(EMM,®s,®s);
if (!regs.h.ah)
return regs.x.bx;
return -1;
}
long emmalloc(int n)
{
/*
śą
da przyznania n stron pami
ę
ci rozbudowanej i zwraca
przypisany im uchwyt plikowy lub -1 w przypadku bł
ę
du
*/
union REGS regs;
regs.h.ah = 0x43;
regs.x.bx = n;
int86(EMM,®s,®s);
if (regs.h.ah)
return -1;
return regs.x.dx;
}
emmmap(long handle, int phys, int page)
{
/*
Mapuje fizyczn
ą
stron
ę
pami
ę
ci rozbudowanej do ramki strony
w 16-kilobajtowym oknie pami
ę
ci konwencjonalnej, aby
umo
Ŝ
liwi
ć
wymian
ę
danych pomi
ę
dzy pami
ę
ci
ą
EMS a
konwencjonaln
ą
.
*/
union REGS regs;
regs.h.ah = 0x44;
regs.h.al = page;
regs.x.bx = phys;
regs.x.dx = handle;
int86(EMM,®s,®s);
return (regs.h.ah == 0);
}
void emmmove(int page, char *str, int n)
{
/*
Przenosi n bajtów z pami
ę
ci konwencjonalnej do okre
ś
lonej
strony pami
ę
ci rozbudowanej.
*/
char far *ptr;
280
Hack Wars. Tom 1. Na tropie hakerów
280
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
ptr = emmbase + page * 16384;
while(n-- > 0)
*ptr++ = *str++;
}
void emmget(int page, char *str, int n)
{
/*
Przenosi n bajtów z okre
ś
lonej strony pami
ę
ci rozbudowanej
do pami
ę
ci konwencjonalnej
*/
char far *ptr;
ptr = emmbase + page * 16384;
while(n-- > 0)
*str++ = *ptr++;
}
emmclose(long handle)
{
/*
Zwalnia strony pami
ę
ci EMS alokowane do uchwytu 'handle'
*/
union REGS regs;
regs.h.ah = 0x45;
regs.x.dx = handle;
int86(EMM,®s,®s);
return (regs.h.ah == 0);
}
/* Funkcja testuj
ą
ca zdefiniowane wy
Ŝ
ej. */
void main()
{
long emmhandle;
long avail;
char teststr[80];
int i;
if(!emmtest())
{
printf("Pami
ęć
EMS nie jest dost
ę
pna\n");
exit(0);
}
if(!emmok())
{
printf("Nie mo
Ŝ
na odnale
źć
mened
Ŝ
era pami
ę
ci EMS\n");
exit(0);
}
avail = emmavail();
if (avail == -1)
{
printf("Bł
ą
d mened
Ŝ
era pami
ę
ci EMS\n");
exit(0);
}
printf("Liczba dost
ę
pnych stron: %ld\n", avail);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
281
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
281
/*
śą
danie 10 stron pami
ę
ci EMS */
if((emmhandle = emmalloc(10)) < 0)
{
printf("Brak pami
ę
ci\n");
exit(0);
}
for (i = 0; i < 10; i++)
{
sprintf(teststr,"%02d Dane testowe\n",i);
emmmap(emmhandle,i,0);
emmmove(0,teststr,strlen(teststr) + 1);
}
for (i = 0; i < 10; i++)
{
emmmap(emmhandle,i,0);
emmget(0,teststr,strlen(teststr) + 1);
printf("ODCZYT BLOKU %d: %s\n",i,teststr);
}
emmclose(emmhandle);
}
Dostęp do pamięci rozszerzonej
Pamięć rozszerzona wyparła niemal całkowicie pamięć rozbudowaną, bo jest szybsza
i prostsza w uŜyciu. Podobnie jednak jak w przypadku pamięci rozbudowanej, nie jest
moŜliwy dostęp do pamięci rozszerzonej w trybie standardowym systemu DOS —
przesyłanie danych realizowane jest za pośrednictwem bufora w pamięci
konwencjonalnej (pamięci „trybu rzeczywistego”). Proces kopiowania danych do
pamięci rozszerzonej jest więc dwuetapowy: skopiowanie danych do bufora i dopiero
z niego — do pamięci rozszerzonej.
Przed rozpoczęciem korzystania z pamięci rozszerzonej program powinien sprawdzić,
czy jest ona dostępna. Przedstawiona funkcja
XMS_init()
przeprowadza taki test, po
czym wywołuje kolejną funkcję,
GetXMSEntry()
, która inicjuje korzystanie z
pamięci rozszerzonej przez program. Dodatkowo alokowany jest bufor przesyłania
danych w pamięci konwencjonalnej.
/*
BLOCKSIZE b
ę
dzie rozmiarem bufora w pami
ę
ci konwencjonalnej,
przeznaczonego do wymiany danych z pami
ę
ci
ą
XMS
(wielokrotno
ść
1-kilobajtowej jednostki alokacji XMS)
*/
#ifdef __SMALL__
#define BLOCKSIZE (16L * 1024L)
#endif
#ifdef __MEDIUM__
#define BLOCKSIZE (16L * 1024L)
#endif
#ifdef __COMPACT__
282
Hack Wars. Tom 1. Na tropie hakerów
282
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
#define BLOCKSIZE (64L * 1024L)
#endif
#ifdef __LARGE__
#define BLOCKSIZE (64L * 1024L)
#endif
char XMS_init()
{
/*
zwraca 0, gdy istnieje pami
ęć
XMS,
1 - gdy nie istnieje
2 - je
Ŝ
eli nie mo
Ŝ
na zaalokowa
ć
bufora wymiany danych w
pami
ę
ci konwencjonalnej
*/
unsigned char status;
_AX=0x4300;
geninterrupt(0x2F);
status = _AL;
if(status==0x80)
{
GetXMSEntry();
XMSBuf = (char far *) farmalloc(BLOCKSIZE);
if (XMSBuf == NULL)
return 2;
return 0;
}
return 1;
}
void GetXMSEntry(void)
{
/*
Ustawia XMSFunc na punkt wej
ś
cia mened
Ŝ
era XMS
co umo
Ŝ
liwi pó
ź
niejsze wywołania
*/
_AX=0x4310;
geninterrupt(0x2F);
XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
}
Po potwierdzeniu obecności pamięci rozszerzonej w systemie kolejna funkcja moŜe
ustalić, jaka jej ilość jest dostępna:
void XMSSize(int *kbAvail, int *largestAvail)
{
/*
Zwraca wielko
ść
dost
ę
pnej pami
ę
ci oraz najwi
ę
kszego
dost
ę
pnego bloku (w kilobajtach)
*/
_AH=8;
(*XMSFunc)();
*largestAvail=_DX;
*kbAvail=_AX;
}
Kolejna funkcja alokuje blok pamięci rozszerzonej, w podobny sposób jak przy
alokacji pamięci konwencjonalnej:
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
283
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
283
char AllocXMS(unsigned long numberBytes)
{
/*
Alokacja bloku pami
ę
ci XMS wielko
ś
ci 'numberBytes'
zwraca 1, gdy zako
ń
czona poprawnie
0 w przypadku bł
ę
du
*/
_DX = (int)(numberBytes / 1024);
_AH = 9;
(*XMSFunc)();
if (_AX==0)
{
return 0;
}
XMSHandle=_DX;
return 1;
}
System DOS nie zwalnia zaalokowanej pamięci rozszerzonej automatycznie. Przed
zakończeniem program musi więc zadbać o „uwolnienie” przyznanych mu wcześniej
bloków. Wykona to kolejna funkcja, zwalniająca blok pamięci zaalokowany
wcześniej przez funkcję
AllocXMS()
:
void XMS_free(void)
{
/*
Zwolnij pami
ęć
XMS
*/
_DX=XMSHandle;
_AH=0x0A;
(*XMSFunc)();
}
Kolejne dwie dłuŜsze funkcje słuŜą do wymiany danych: jedna zapisuje dane do
pamięci rozszerzonej, druga kopiuje dane z pamięci rozszerzonej do pamięci
konwencjonalnej.
/*
Struktura XMSParms słu
Ŝ
y do wymiany danych pomi
ę
dzy pami
ę
ci
ą
trybu rzeczywistego, a XMS
*/
struct parmstruct
{
/*
rozmiar kopiowanego bloku w bajtach
*/
unsigned long blockLength;
/*
sourceHandle to uchwyt XMS obszaru
ź
ródłowego; 0 oznacza,
Ŝ
e sourcePtr b
ę
dzie wska
ź
nikiem trybu rzeczywistego
(16:16); w pozostałych przypadkach sourcePtr jest
32-bitowym przesuni
ę
ciem od pocz
ą
tku obszaru XMS
okre
ś
lonego przez sourceHandle
*/
unsigned int sourceHandle;
far void *sourcePtr;
284
Hack Wars. Tom 1. Na tropie hakerów
284
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
/*
destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,
Ŝ
e destPtr b
ę
dzie wska
ź
nikiem trybu rzeczywistego
(16:16); w pozostałych przypadkach destPtr jest
32-bitowym przesuni
ę
ciem od pocz
ą
tku obszaru XMS
okre
ś
lonego przez destHandle
*/
unsigned int destHandle;
far void *destPtr;
}
XMSParms;
char XMS_write(unsigned long loc, char far *val, unsigned length)
{
/*
Zaokr
ą
glij długo
ść
do kolejnej warto
ś
ci parzystej
*/
length += length % 2;
XMSParms.sourceHandle=0;
XMSParms.sourcePtr=val;
XMSParms.destHandle=XMSHandle;
XMSParms.destPtr=(void far *) (loc);
XMSParms.blockLength=length; /* Musi by
ć
liczb
ą
parzyst
ą
! */
_SI = FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return 0;
}
return 1;
}
void *XMS_read(unsigned long loc,unsigned length)
{
/*
Zwraca wska
ź
nik do danych
lub NULL w przypadku bł
ę
du
*/
/*
Zaokr
ą
glij długo
ść
do kolejnej warto
ś
ci parzystej
*/
length += length % 2;
XMSParms.sourceHandle=XMSHandle;
XMSParms.sourcePtr=(void far *) (loc);
XMSParms.destHandle=0;
XMSParms.destPtr=XMSBuf;
XMSParms.blockLength=length; /* Musi by
ć
liczb
ą
parzyst
ą
*/
_SI=FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return NULL;
}
return XMSBuf;
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
285
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
285
Czas teraz połączyć przedstawione dotąd funkcje w jednym programie:
/* Sekwencyjna tabela rekordów zmiennej długo
ś
ci
w pami
ę
ci XMS */
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <string.h>
#define TRUE 1
#define FALSE 0
/*
BLOCKSIZE b
ę
dzie rozmiarem bufora w pami
ę
ci konwencjonalnej,
przeznaczonego do wymiany danych z pami
ę
ci
ą
XMS
(wielokrotno
ść
1-kilobajtowej jednostki alokacji XMS)
*/
#ifdef __SMALL__
#define BLOCKSIZE (16L * 1024L)
#endif
#ifdef __MEDIUM__
#define BLOCKSIZE (16L * 1024L)
#endif
#ifdef __COMPACT__
#define BLOCKSIZE (64L * 1024L)
#endif
#ifdef __LARGE__
#define BLOCKSIZE (64L * 1024L)
#endif
/*
Struktura XMSParms słu
Ŝ
y do wymiany danych pomi
ę
dzy pami
ę
ci
ą
trybu rzeczywistego, a XMS
*/
struct parmstruct
{
/*
rozmiar kopiowanego bloku w bajtach
*/
unsigned long blockLength;
/*
sourceHandle to uchwyt XMS obszaru
ź
ródłowego; 0 oznacza,
Ŝ
e sourcePtr b
ę
dzie wska
ź
nikiem trybu rzeczywistego
(16:16); w pozostałych przypadkach sourcePtr jest
32-bitowym przesuni
ę
ciem od pocz
ą
tku obszaru XMS
okre
ś
lonego przez sourceHandle
*/
unsigned int sourceHandle;
far void *sourcePtr;
/*
286
Hack Wars. Tom 1. Na tropie hakerów
286
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
destHandle to uchwyt XMS obszaru docelowego; 0 oznacza,
Ŝ
e destPtr b
ę
dzie wska
ź
nikiem trybu rzeczywistego
(16:16); w pozostałych przypadkach destPtr jest
32-bitowym przesuni
ę
ciem od pocz
ą
tku obszaru XMS
okre
ś
lonego przez destHandle
*/
unsigned int destHandle;
far void *destPtr;
}
XMSParms;
void far (*XMSFunc) (void); /* Posłu
Ŝ
y do wywołania mened
Ŝ
era XMS
(himem.sys) */
char GetBuf(void);
void GetXMSEntry(void);
char *XMSBuf; /* Bufor wymiany danych, w pami
ę
ci konwencjonalnej */
unsigned int XMSHandle; /* uchwyt do zaalokowanego bloku XMS */
char XMS_init()
{
/*
zwraca 0, gdy istnieje pami
ęć
XMS,
1 - gdy nie istnieje
2 - je
Ŝ
eli nie mo
Ŝ
na zaalokowa
ć
bufora wymiany danych w
pami
ę
ci konwencjonalnej
*/
unsigned char status;
_AX=0x4300;
geninterrupt(0x2F);
status = _AL;
if(status==0x80)
{
GetXMSEntry();
XMSBuf = (char far *) farmalloc(BLOCKSIZE);
if (XMSBuf == NULL)
return 2;
return 0;
}
return 1;
}
void GetXMSEntry(void)
{
/*
Ustawia XMSFunc na punkt wej
ś
cia mened
Ŝ
era XMS
co umo
Ŝ
liwi pó
ź
niejsze wywołania
*/
_AX=0x4310;
geninterrupt(0x2F);
XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
}
void XMSSize(int *kbAvail, int *largestAvail)
{
/*
Zwraca wielko
ść
dost
ę
pnej pami
ę
ci oraz najwi
ę
kszego
dost
ę
pnego bloku (w kilobajtach)
*/
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
287
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
287
_AH=8;
(*XMSFunc)();
*largestAvail=_DX;
*kbAvail=_AX;
}
char AllocXMS(unsigned long numberBytes)
{
/*
Alokacja bloku pami
ę
ci XMS wielko
ś
ci 'numberBytes'
Zwraca 1, gdy zako
ń
czona poprawnie
0 w przypadku bł
ę
du
*/
_DX = (int)(numberBytes / 1024);
_AH = 9;
(*XMSFunc)();
if (_AX==0)
{
return FALSE;
}
XMSHandle=_DX;
return TRUE;
}
void XMS_free(void)
{
/*
Zwolnij pami
ęć
XMS
*/
_DX=XMSHandle;
_AH=0x0A;
(*XMSFunc)();
}
char XMS_write(unsigned long loc, char far *val, unsigned length)
{
/*
Zaokr
ą
glij długo
ść
do kolejnej warto
ś
ci parzystej
*/
length += length % 2;
XMSParms.sourceHandle=0;
XMSParms.sourcePtr=val;
XMSParms.destHandle=XMSHandle;
XMSParms.destPtr=(void far *) (loc);
XMSParms.blockLength=length; /* Musi by
ć
liczb
ą
parzyst
ą
! */
_SI = FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return FALSE;
}
return TRUE;
}
void *XMS_read(unsigned long loc,unsigned length)
{
/*
Zwraca wska
ź
nik do danych
288
Hack Wars. Tom 1. Na tropie hakerów
288
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
lub NULL w przypadku bł
ę
du
*/
/*
Zaokr
ą
glij długo
ść
do kolejnej warto
ś
ci parzystej
*/
length += length % 2;
XMSParms.sourceHandle=XMSHandle;
XMSParms.sourcePtr=(void far *) (loc);
XMSParms.destHandle=0;
XMSParms.destPtr=XMSBuf;
XMSParms.blockLength=length; /* Musi by
ć
liczb
ą
parzyst
ą
*/
_SI=FP_OFF(&XMSParms);
_AH=0x0B;
(*XMSFunc)();
if (_AX==0)
{
return NULL;
}
return XMSBuf;
}
/*
Przykładowy program
Zapisuje ci
ą
gi ró
Ŝ
nej długo
ś
ci
do pojedycznego bloku XMS(EMB)
i odczytuje je
*/
int main()
{
int kbAvail,largestAvail;
char buffer[80];
char *p;
long pos;
long end;
if (XMS_init() == 0)
printf("Pami
ęć
XMS dost
ę
pna ...\n");
else
{
printf("Pami
ęć
XMS nie jest dost
ę
pna\n");
return(1);
}
XMSSize(&kbAvail,&largestAvail);
printf("Wielko
ść
dost
ę
pnej pami
ę
ci XMS: %d; Largest block:
%dK\n",kbAvail,largestAvail);
if (!AllocXMS(2000 * 1024L))
return(1);
pos = 0;
do
{
p = fgets(buffer,1000,stdin);
if (p != NULL)
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
289
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
289
XMS_write(pos,buffer,strlen(buffer) + 1);
pos += strlen(buffer) + 1;
}
}
while(p != NULL);
end = pos;
pos = 0;
do
{
memcpy(buffer,XMS_read(pos,100),70);
printf("%s",buffer);
pos += strlen(buffer) + 1;
}
while(pos < end);
/*
Zwolnienie pami
ę
ci XMS jest BARDZO wa
Ŝ
ne!
*/
XMS_free();
return 0;
}
Tworzenie programów TSR
Ostatnim tematem w naszym omówieniu podstaw języka C, mającym zarazem
szczególne znaczenie dla zagadnień zabezpieczeń, są wszechstronne programy
rezydentne, określane równieŜ skrótem TSR (terminate and stay resident, zakończ i
pozostań w pamięci). Programy takie pozostają aktywne w pamięci, w trakcie gdy „na
pierwszym planie” pracują inne programy. Tworzenie programów rezydentnych to
ulubione zajęcie wielu programistów i hakerów.
Trudność w tworzeniu programów TSR wynika z ograniczeń DOS-u, który nie jest
wielozadaniowym systemem operacyjnym i nie współpracuje dobrze z kodem
wielowątkowym, przede wszystkim gdy pojawiają się wzajemne wywołania funkcji
systemowych (przerwań). Teoria programów rezydentnych jest dość prosta. Program
taki kończy pracę funkcją DOS-u keep (przerwanie 27h), a nie przy uŜyciu
standardowej funkcji terminate. Zapewnia to zarezerwowanie w pamięci obszaru
wykorzystywanego przez program. Nie jest to skomplikowane, koniecznie trzeba
jednak podać, jaka ilość pamięci ma zostać zarezerwowana.
Głównym źródłem problemów jest brak moŜliwości korzystania z wywołań funkcji
DOS-u od momentu przyjęcia przez programu statusu programu rezydentnego.
PoniŜej przedstawiamy kilka podstawowych zasad, których przestrzeganie pomoŜe
uniknąć problemów z programami TSR.
1.
Unikanie wywołań funkcji DOS-u.
2.
Monitorowanie znacznika zajętości DOS-u (busy flag). Gdy ma on wartość
niezerową, oznacza to, Ŝe DOS wykonuje właśnie funkcję 21h i nie moŜna
mu przeszkadzać!
290
Hack Wars. Tom 1. Na tropie hakerów
290
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
3.
Monitorowanie przerwania 28h. Sygnalizuje ono, Ŝe DOS jest zajęty
oczekiwaniem na dane wejściowe z konsoli. W tym czasie moŜna wykonywać
róŜne operacje bez względu na stan znacznika zajętości.
4.
Sprawdzanie, czy program jest juŜ uruchomiony, aby uniknąć jego wielokrotnego
ładowania do pamięci.
5.
Nie wolno zapominać, Ŝe równieŜ inne programy TSR mogą przechwytywać
przerwania. Konieczne jest więc tworzenie „łańcucha” — po wywołaniu własnej
funkcji wywołujemy funkcję określoną wektorem zastanym przy uruchamianiu
programu.
6.
Program TSR musi korzystać z własnego stosu, a nie stosu uruchomionego
procesu.
7.
Programy TSR naleŜy kompilować, wybierając model pamięci small i wyłączając
sprawdzanie stosu.
8.
Po przejęciu kontroli program TSR musi poinformować DOS o zmianie procesu
aktywnego.
Przedstawione poniŜej trzy moduły kodu stanowią kompletny program rezydentny.
Jest to prosta baza danych typu „ksiąŜka adresowa”, aktywowana w trakcie pracy
innych programów wciśnięciem kombinacji klawiszy ALT i kropki (.). JeŜeli praca
systemu operacyjnego nie moŜe zostać przerwana, program nie reaguje na
kombinację klawiszy. Wówczas naleŜy spróbować ponownie.
/*
Przykładowy program TSR (wyskakuj
ą
ca ksi
ąŜ
ka adresowa)
Kompilowa
ć
z modelem pami
ę
ci 'small' i
wył
ą
czonym sprawdzaniem stosu (stack checking)
*/
#include <dos.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>
static union REGS rg;
/*
Rozmiar programu pozostaj
ą
cego jako rezydentny
Aby okre
ś
li
ć
wielko
ść
minimaln
ą
, konieczne s
ą
eksperymenty
*/
unsigned sizeprogram = 28000/16;
/* Aktywacja kombinacj
ą
'Alt+.' */
unsigned scancode = 52; /* . */
unsigned keymask = 8; /* ALT */
char signature[]= "POPADDR";
char fpath[40];
/*
Prototypy funkcji
*/
void curr_cursor(int *x, int *y);
int resident(char *, void interrupt(*)());
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
291
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
291
void resinit(void);
void terminate(void);
void restart(void);
void wait(void);
void resident_psp(void);
void exec(void);
/*
Punkt wej
ś
cia z DOS-u
*/
void main(int argc, char *argv[])
{
void interrupt ifunc();
int ivec;
/*
Dla uproszczenia zakładamy,
Ŝ
e plik danych jest w
katalogu głównym dysku C:
*/
strcpy(fpath,"C:\\ADDRESS.DAT");
if ((ivec = resident(signature,ifunc)) != 0)
{
/* Ju
Ŝ
jest */
if (argc > 1)
{
rg.x.ax = 0;
if (strcmp(argv[1],"quit") == 0)
rg.x.ax = 1;
else if (strcmp(argv[1],"restart") == 0)
rg.x.ax = 2;
else if (strcmp(argv[1],"wait") == 0)
rg.x.ax = 3;
if (rg.x.ax)
{
int86(ivec,&rg,&rg);
return;
}
}
printf("\nKsi
ąŜ
ka adresowa jest ju
Ŝ
uruchomiona");
}
else
{
/* Załadowanie programu TSR */
printf("Ksi
ąŜ
ka adresowa została uruchomiona.\nWci
ś
nij 'Alt+.',
aby
wywoła
ć
...\n");
resinit();
}
}
void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)
{
if(ax == 1)
terminate();
else if(ax == 2)
restart();
else if(ax == 3)
wait();
}
292
Hack Wars. Tom 1. Na tropie hakerów
292
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
popup()
{
int x,y;
curr_cursor(&x,&y);
/* Wywołujemy program rezydentny C */
exec();
cursor(x,y);
}
/*
Drugi moduł
*/
#include <dos.h>
#include <stdio.h>
static union REGS rg;
static struct SREGS seg;
static unsigned mcbseg;
static unsigned dosseg;
static unsigned dosbusy;
static unsigned enddos;
char far *intdta;
static unsigned intsp;
static unsigned intss;
static char far *mydta;
static unsigned myss;
static unsigned stack;
static unsigned ctrl_break;
static unsigned mypsp;
static unsigned intpsp;
static unsigned pids[2];
static int pidctr = 0;
static int pp;
static void interrupt (*oldtimer)();
static void interrupt (*old28)();
static void interrupt (*oldkb)();
static void interrupt (*olddisk)();
static void interrupt (*oldcrit)();
void interrupt newtimer();
void interrupt new28();
void interrupt newkb();
void interrupt newdisk();
void interrupt newcrit();
extern unsigned sizeprogram;
extern unsigned scancode;
extern unsigned keymask;
static int resoff = 0;
static int running = 0;
static int popflg = 0;
static int diskflag = 0;
static int kbval;
static int cflag;
void dores(void);
void pidaddr(void);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
293
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
293
void resinit()
{
segread(&seg);
myss = seg.ss;
rg.h.ah = 0x34;
intdos(&rg,&rg);
dosseg = _ES;
dosbusy = rg.x.bx;
mydta = getdta();
pidaddr();
oldtimer = getvect(0x1c);
old28 = getvect(0x28);
oldkb = getvect(9);
olddisk = getvect(0x13);
setvect(0x1c,newtimer);
setvect(9,newkb);
setvect(0x28,new28);
setvect(0x13,newdisk);
stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;
rg.x.ax = 0x3100;
rg.x.dx = sizeprogram;
intdos(&rg,&rg);
}
void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
diskflag++;
(*olddisk)();
ax = _AX;
newcrit();
flgs = cflag;
--diskflag;
}
void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
ax = 0;
cflag = flgs;
}
void interrupt newkb()
{
if (inportb(0x60) == scancode)
{
kbval = peekb(0,0x417);
if (!resoff && ((kbval & keymask) ^ keymask) == 0)
{
kbval = inportb(0x61);
outportb(0x61,kbval | 0x80);
outportb(0x61,kbval);
disable();
outportb(0x20,0x20);
enable();
if (!running)
popflg = 1;
return;
}
}
294
Hack Wars. Tom 1. Na tropie hakerów
294
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
(*oldkb)();
}
void interrupt newtimer()
{
(*oldtimer)();
if (popflg && peekb(dosseg,dosbusy) == 0)
if(diskflag == 0)
{
outportb(0x20,0x20);
popflg = 0;
dores();
}
}
void interrupt new28()
{
(*old28)();
if (popflg && peekb(dosseg,dosbusy) != 0)
{
popflg = 0;
dores();
}
}
resident_psp()
{
intpsp = peek(dosseg,*pids);
for(pp = 0; pp < pidctr; pp++)
poke(dosseg,pids[pp],mypsp);
}
interrupted_psp()
{
for(pp = 0; pp < pidctr; pp++)
poke(dosseg,pids[pp],intpsp);
}
void dores()
{
running = 1;
disable();
intsp = _SP;
intss = _SS;
_SP = stack;
_SS = myss;
enable();
oldcrit = getvect(0x24);
setvect(0x24,newcrit);
rg.x.ax = 0x3300;
intdos(&rg,&rg);
ctrl_break = rg.h.dl;
rg.x.ax = 0x3301;
rg.h.dl = 0;
intdos(&rg,&rg);
intdta = getdta();
setdta(mydta);
resident_psp();
popup();
interrupted_psp();
setdta(intdta);
setvect(0x24,oldcrit);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
295
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
295
rg.x.ax = 0x3301;
rg.h.dl = ctrl_break;
intdos(&rg,&rg);
disable();
_SP = intsp;
_SS = intss;
enable();
running = 0;
}
static int avec = 0;
unsigned resident(char *signature,void interrupt(*ifunc)())
{
char *sg;
unsigned df;
int vec;
segread(&seg);
df = seg.ds-seg.cs;
for(vec = 0x60; vec < 0x68; vec++)
{
if (getvect(vec) == NULL)
{
if (!avec)
avec = vec;
continue;
}
for(sg = signature; *sg; sg++)
if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))
break;
if (!*sg)
return vec;
}
if (avec)
setvect(avec,ifunc);
return 0;
}
static void pidaddr()
{
unsigned adr = 0;
rg.h.ah = 0x51;
intdos(&rg,&rg);
mypsp = rg.x.bx;
rg.h.ah = 0x52;
intdos(&rg,&rg);
enddos = _ES;
enddos = peek(enddos,rg.x.bx-2);
while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos <<4))
{
if (peek(dosseg,adr) == mypsp)
{
rg.h.ah = 0x50;
rg.x.bx = mypsp + 1;
intdos(&rg,&rg);
if (peek(dosseg,adr) == mypsp + 1)
pids[pidctr++] = adr;
rg.h.ah = 0x50;
rg.x.bx = mypsp;
intdos(&rg,&rg);
296
Hack Wars. Tom 1. Na tropie hakerów
296
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
}
adr++;
}
}
static resterm()
{
setvect(0x1c,oldtimer);
setvect(9,oldkb);
setvect(0x28,old28);
setvect(0x13,olddisk);
setvect(avec,(void interrupt (*)()) 0);
rg.h.ah = 0x52;
intdos(&rg,&rg);
mcbseg = _ES;
mcbseg = peek(mcbseg,rg.x.bx-2);
segread(&seg);
while(peekb(mcbseg,0) == 0x4d)
{
if(peek(mcbseg,1) == mypsp)
{
rg.h.ah = 0x49;
seg.es = mcbseg+1;
intdosx(&rg,&rg,&seg);
}
mcbseg += peek(mcbseg,3) + 1;
}
}
terminate()
{
if (getvect(0x13) == (void interrupt (*)()) newdisk)
if (getvect(9) == newkb)
if(getvect(0x28) == new28)
if(getvect(0x1c) == newtimer)
{
resterm();
return;
}
resoff = 1;
}
restart()
{
resoff = 0;
}
wait()
{
resoff = 1;
}
void cursor(int y, int x)
{
rg.x.ax = 0x0200;
rg.x.bx = 0;
rg.x.dx = ((y << 8) & 0xff00) + x;
int86(16,&rg,&rg);
}
void curr_cursor(int *y, int *x)
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
297
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
297
{
rg.x.ax = 0x0300;
rg.x.bx = 0;
int86(16,&rg,&rg);
*x = rg.h.dl;
*y = rg.h.dh;
}
/*
Trzeci moduł, przykładowa ksi
ąŜ
ka adresowa
z obsług
ą
myszy
*/
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>
#include <dos.h>
#include <conio.h>
#include <graphics.h>
#include <bios.h>
/* left nie mo
Ŝ
e mie
ć
warto
ś
ci mniejszej od 3 */
#define left 4
/* Struktura rekordu */
typedef struct
{
char name[31];
char company[31];
char address[31];
char area[31];
char town[31];
char county[31];
char post[13];
char telephone[16];
char fax[16];
}
data;
extern char fpath[];
static char scr[4000];
static char sbuff[2000];
char stext[30];
data rec;
int handle;
int recsize;
union REGS inreg,outreg;
/*
Prototypy funkcji
*/
void FATAL(char *);
void OPENDATA(void);
void CONTINUE(void);
void EXPORT_MULTI(void);
void GETDATA(int);
int GETOPT(void);
298
Hack Wars. Tom 1. Na tropie hakerów
298
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
void DISPDATA(void);
void ADD_REC(void);
void PRINT_MULTI(void);
void SEARCH(void);
void MENU(void);
int GET_MOUSE(int *buttons)
{
inreg.x.ax = 0;
int86(0x33,&inreg,&outreg);
*buttons = outreg.x.bx;
return outreg.x.ax;
}
void MOUSE_CURSOR(int status)
{
/* Status = 0 kursor wył
ą
czony */
/* 1 kursor wł
ą
czony */
inreg.x.ax = 2 - status;
int86(0x33,&inreg,&outreg);
}
int MOUSE_LOCATION(int *x, int *y)
{
inreg.x.ax = 3;
int86(0x33,&inreg,&outreg);
*x = outreg.x.cx / 8;
*y = outreg.x.dx / 8;
return outreg.x.bx;
}
int GETOPT()
{
int result;
int x;
int y;
do
{
do
{
result = MOUSE_LOCATION(&x,&y);
if (result & 1)
{
if (x >= 52 && x <= 53 && y >= 7 && y <= 15)
return y - 7;
if (x >= 4 && x <= 40 && y >= 7 && y <= 14)
return y + 10;
if (x >= 4 && x <= 40 && y == 15)
return y + 10;
}
}
while(!bioskey(1));
result = bioskey(0);
x = result & 0xff;
if (x == 0)
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
299
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
299
result = result >> 8;
result -= 60;
}
}
while(result < 0 || result > 8);
return result;
}
void setvideo(unsigned char mode)
{
/* Okre
ś
la tryb wy
ś
wietlania i czy
ś
ci ekran */
inreg.h.al = mode;
inreg.h.ah = 0x00;
int86(0x10, &inreg, &outreg);
}
int activepage(void)
{
/* Zwraca wybran
ą
stron
ę
ekranu */
union REGS inreg,outreg;
inreg.h.ah = 0x0F;
int86(0x10, &inreg, &outreg);
return(outreg.h.bh);
}
void print(char *str)
{
/*
Wyprowadza znaki bezpo
ś
ednio na bie
Ŝą
c
ą
stron
ę
ekranu,
poczynaj
ą
c od bie
Ŝą
cej pozycji kursora. Kursor nie jest
przenoszony.
Funkcja zakłada,
Ŝ
e stosowana jest karta graficzna COLOR.
Aby korzysta
ć
z monochromatycznej karty graficznej,
nale
Ŝ
y zmieni
ć
0xB800 na 0xB000.
*/
int page;
int offset;
unsigned row;
unsigned col;
char far *ptr;
page = activepage();
curr_cursor(&row,&col);
offset = page * 4000 + row * 160 + col * 2;
ptr = MK_FP(0xB800,offset);
while(*str)
{
*ptr++= *str++;
ptr++;
}
}
300
Hack Wars. Tom 1. Na tropie hakerów
300
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
void TRUESHADE(int lef, int top, int right, int bottom)
{
int n;
/* Cieniowanie */
gettext(lef,top,right,bottom,sbuff);
for(n = 1; n < 2000; n+= 2)
sbuff[n] = 7;
puttext(lef,top,right,bottom,sbuff);
}
void DBOX(int l, int t, int r, int b)
{
/* Rysowanie prostok
ą
ta podwójn
ą
lini
ą
*/
int n;
cursor(t,l);
print("É");
for(n = 1; n < r - l; n++)
{
cursor(t,l + n);
print("Í");
}
cursor(t,r);
print("»");
for (n = t + 1; n < b; n++)
{
cursor(n,l);
print("ž");
cursor(n,r);
print("ž");
}
cursor(b,l);
print("E");
for(n = 1; n < r - l; n++)
{
cursor(b,l+n);
print("Í");
}
cursor(b,r);
print("È");
}
int INPUT(char *text,unsigned length)
{
/* Pobranie ci
ą
gu wprowadzonego przez u
Ŝ
ytkownika */
unsigned key_pos;
int key;
unsigned start_row;
unsigned start_col;
unsigned end;
char temp[80];
char *p;
curr_cursor(&start_row,&start_col);
key_pos = 0;
end = strlen(text);
for(;;)
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
301
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
301
key = bioskey(0);
if ((key & 0xFF) == 0)
{
key = key >> 8;
if (key == 79)
{
while(key_pos < end)
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if (key == 71)
{
key_pos = 0;
cursor(start_row,start_col);
}
else
if ((key == 75) && (key_pos > 0))
{
key_pos--;
cursor(start_row,start_col + key_pos);
}
else
if ((key == 77) && (key_pos < end))
{
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if (key == 83)
{
p = text + key_pos;
while(*(p+1))
{
*p = *(p+1);
p++;
}
*p = 32;
if (end > 0)
end--;
cursor(start_row,start_col);
cprintf(text);
cprintf(" ");
if ((key_pos > 0) && (key_pos == end))
key_pos--;
cursor(start_row,start_col + key_pos);
}
}
else
{
key = key & 0xFF;
if (key == 13 || key == 27)
break;
else
if ((key == 8) && (key_pos > 0))
{
end--;
key_pos--;
text[key_pos--] = '\0';
strcpy(temp,text);
p = text + key_pos + 2;
302
Hack Wars. Tom 1. Na tropie hakerów
302
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
strcat(temp,p);
strcpy(text,temp);
cursor(start_row,start_col);
cprintf("%-*.*s",length,length,text);
key_pos++;
cursor(start_row,start_col + key_pos);
}
else
if ((key > 31) && (key_pos < length) &&
(start_col + key_pos < 80))
{
if (key_pos <= end)
{
p = text + key_pos;
memmove(p+1,p,end - key_pos);
if (end < length)
end++;
text[end] = '\0';
}
text[key_pos++] = (char)key;
if (key_pos > end)
{
end++;
text[end] = '\0';
}
cursor(start_row,start_col);
cprintf("%-*.*s",length,length,text);
cursor(start_row,start_col + key_pos);
}
}
}
text[end] = '\0';
return key;
}
void FATAL(char *error)
{
/* Wyst
ą
pił bł
ą
d krytyczny */
printf("\nBŁ
Ą
D KRTYTYCZNY: %s",error);
exit(0);
}
void OPENDATA()
{
/* Sprawd
ź
, czy istnieje plik danych; je
Ŝ
eli nie, utwórz*/
/* je
Ŝ
eli tak, otwórz do odczytu-zapisu na ko
ń
cu pliku */
handle = open(fpath,O_RDWR,S_IWRITE);
if (handle == -1)
{
handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE);
if (handle == -1)
FATAL("Nie mo
Ŝ
na utworzy
ć
pliku danych");
}
/* Odczytaj pierwszy rekord */
read(handle,&rec,recsize);
}
void CLOSEDATA()
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
303
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
303
close(handle);
}
void GETDATA(int start)
{
/* Pobierz adres wprowadzany przez u
Ŝ
ytkownika */
textcolor(BLACK);
textbackground(GREEN);
gotoxy(left,8);
print("Imi
ę
i nazwisko ");
gotoxy(left,9);
print("Firma ");
gotoxy(left,10);
print("Adres ");
gotoxy(left,11);
print("Wojewodztwo ");
gotoxy(left,12);
print("Miasto ");
gotoxy(left,13);
print("Powiat ");
gotoxy(left,14);
print("Kod pocztowy ");
gotoxy(left,15);
print("Telefon ");
gotoxy(left,16);
print("Faks ");
switch(start)
{
case 0: gotoxy(left + 10,8);
if(INPUT(rec.name,30) == 27)
break;
case 1: gotoxy(left + 10,9);
if(INPUT(rec.company,30) == 27)
break;
case 2: gotoxy(left + 10,10);
if(INPUT(rec.address,30) == 27)
break;
case 3: gotoxy(left + 10,11);
if(INPUT(rec.area,30) == 27)
break;
case 4: gotoxy(left + 10,12);
if(INPUT(rec.town,30) == 27)
break;
case 5: gotoxy(left + 10,13);
if(INPUT(rec.county,30) == 27)
break;
case 6: gotoxy(left + 10,14);
if(INPUT(rec.post,12) == 27)
break;
case 7: gotoxy(left + 10,15);
if(INPUT(rec.telephone,15) == 27)
break;
case 8: gotoxy(left + 10,16);
INPUT(rec.fax,15);
break;
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
print(" ");
}
304
Hack Wars. Tom 1. Na tropie hakerów
304
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
void DISPDATA()
{
/* Wy
ś
wietl dane adresowe */
textcolor(BLACK);
textbackground(GREEN);
cursor(7,3);
cprintf("Imi
ę
i nazwisko %-30.30s",rec.name);
cursor(8,3);
cprintf("Firma %-30.30s",rec.company);
cursor(9,3);
cprintf("Adres %-30.30s",rec.address);
cursor(10,3);
cprintf("Województwo %-30.30s",rec.area);
cursor(11,3);
cprintf("Miasto %-30.30s",rec.town);
cursor(12,3);
cprintf("Powiat %-30.30s",rec.county);
cursor(13,3);
cprintf("Kod pocztowy %-30.30s",rec.post);
cursor(14,3);
cprintf("Telefon %-30.30s",rec.telephone);
cursor(15,3);
cprintf("Faks %-30.30s",rec.fax);
}
int LOCATE(char *text)
{
int result;
do
{
/* Załaduj rekord do pami
ę
ci */
result = read(handle,&rec,recsize);
if (result > 0)
{
/* Szukanie dopasowania w danym rekordzie */
if (strstr(strupr(rec.name),text) != NULL)
return(1);
if (strstr(strupr(rec.company),text) != NULL)
return(1);
if (strstr(strupr(rec.address),text) != NULL)
return(1);
if (strstr(strupr(rec.area),text) != NULL)
return(1);
if (strstr(strupr(rec.town),text) != NULL)
return(1);
if (strstr(strupr(rec.county),text) != NULL)
return(1);
if (strstr(strupr(rec.post),text) != NULL)
return(1);
if (strstr(strupr(rec.telephone),text) != NULL)
return(1);
if (strstr(strupr(rec.fax),text) != NULL)
return(1);
}
}
while(result > 0);
return(0);
}
void SEARCH()
{
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
305
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
305
int result;
gotoxy(left,21);
textcolor(WHITE);
textbackground(RED);
cprintf("Wprowad
ź
wzorzec wyszukiwania ");
strcpy(stext,"");
INPUT(stext,30);
if (*stext == 0)
{
gotoxy(left,21);
cprintf("%70c",32);
return;
}
gotoxy(left,21);
textcolor(WHITE);
textbackground(RED);
cprintf("Wyszukiwanie %s Prosz
ę
czeka
ć
....",stext);
strupr(stext);
/* Zlokalizuj pocz
ą
tek pliku */
lseek(handle,0,SEEK_SET);
result = LOCATE(stext);
if (result == 0)
{
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left + 27,21);
cprintf("Brak pasuj
ą
cych rekordów");
gotoxy(left + 24,22);
cprintf("Wci
ś
nij ENTER, aby kontynuowa
ć
");
bioskey(0);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf("%70c",32);
}
else
{
lseek(handle,0 - recsize,SEEK_CUR);
read(handle,&rec,recsize);
DISPDATA();
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("%70c",32);
textcolor(BLACK);
textbackground(GREEN);
}
void CONTINUE()
{
int result;
long curpos;
curpos = tell(handle) - recsize;
result = LOCATE(stext);
textcolor(WHITE);
textbackground(RED);
if (result == 0)
{
306
Hack Wars. Tom 1. Na tropie hakerów
306
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
gotoxy(left + 24,21);
cprintf("Brak dalszych rekordów");
gotoxy(left + 24,22);
cprintf("Wci
ś
nij ENTER, aby kontynuowa
ć
");
bioskey(0);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf("%70c",32);
lseek(handle,curpos,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();
}
else
{
lseek(handle,0 - recsize,SEEK_CUR);
read(handle,&rec,recsize);
DISPDATA();
}
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("%70c",32);
gotoxy(left,22);
cprintf(" ");
textcolor(BLACK);
textbackground(GREEN);
}
void PRINT_MULTI()
{
data buffer;
char destination[60];
char text[5];
int result;
int ok;
int ok2;
int blanks;
int total_lines;
char *p;
FILE *fp;
textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
cprintf("Podaj kryterium wyboru");
/* Usu
ń
wcze
ś
niejsze dane rekordu */
memset(&rec,0,recsize);
DISPDATA();
GETDATA(0);
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("Podaj, gdzie przesła
ć
PRN");
strcpy(destination,"PRN");
gotoxy(left,22);
cprintf("Podaj długo
ść
adresu w wierszach 18");
strcpy(text,"18");
gotoxy(left + 25,21);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
307
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
307
INPUT(destination,40);
gotoxy(left +30,22);
INPUT(text,2);
gotoxy(left,21);
cprintf("%72c",32);
gotoxy(left,22);
cprintf("%72c",32);
total_lines = atoi(text) - 6;
if (total_lines < 0)
total_lines = 0;
fp = fopen(destination,"w+");
if (fp == NULL)
{
gotoxy(left,21);
cprintf("Nie mo
Ŝ
na drukowa
ć
do %s",destination);
gotoxy(left,22);
cprintf("Wci
ś
nij ENTER, aby kontynuowa
ć
");
bioskey(0);
gotoxy(left,21);
cprintf("%78c",32);
gotoxy(left,22);
cprintf(" ");
}
/* Zlokalizuj pocz
ą
tek pliku */
lseek(handle,0,SEEK_SET);
do
{
/* Załaduj rekord do pami
ę
ci */
result = read(handle,&buffer,recsize);
if (result > 0)
{
ok = 1;
/* Szukaj danych w rekordzie */
if (*rec.name)
if (stricmp(buffer.name,rec.name))
ok = 0;
if (*rec.company)
if (stricmp(buffer.company,rec.company))
ok = 0;
if (*rec.address)
if (stricmp(buffer.address,rec.address))
ok = 0;
if (*rec.area)
if (stricmp(buffer.area,rec.area))
ok = 0;
if (*rec.town)
if (stricmp(buffer.town,rec.town))
ok = 0;
if (*rec.county)
if (stricmp(buffer.county,rec.county))
ok = 0;
if (*rec.post)
if (stricmp(buffer.post,rec.post))
ok = 0;
if (*rec.telephone)
if (stricmp(buffer.telephone,rec.telephone))
ok = 0;
if (*rec.fax)
308
Hack Wars. Tom 1. Na tropie hakerów
308
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
if (stricmp(buffer.fax,rec.fax))
ok = 0;
if (ok)
{
blanks = total_lines;
p = buffer.name;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.name);
p = buffer.company;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.company);
p = buffer.address;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.address);
p = buffer.area;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
309
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
309
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.area);
p = buffer.town;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.town);
p = buffer.county;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.county);
p = buffer.post;
ok2 = 0;
while(*p)
{
if (*p != 32)
{
ok2 = 1;
break;
}
p++;
}
if (!ok2)
blanks++;
else
fprintf(fp,"%s\n",buffer.post);
while(blanks)
{
fprintf(fp,"\n");
blanks--;
}
}
}
}
while(result > 0);
fclose(fp);
lseek(handle,0,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();
310
Hack Wars. Tom 1. Na tropie hakerów
310
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
}
void EXPORT_MULTI()
{
data buffer;
char destination[60];
int result;
int ok;
FILE *fp;
textcolor(WHITE);
textbackground(RED);
gotoxy(left + 23,21);
cprintf("Wprowad
ź
kryterium wyboru");
/* Usu
ń
wcze
ś
niejsze dane rekordu */
memset(&rec,0,recsize);
DISPDATA();
GETDATA(0);
textcolor(WHITE);
textbackground(RED);
gotoxy(left,21);
cprintf("Wprowad
ź
nazw
ę
pliku address.txt");
strcpy(destination,"address.txt");
gotoxy(left + 18,21);
INPUT(destination,59);
gotoxy(left,21);
cprintf("%70c",32);
fp = fopen(destination,"w+");
if (fp == NULL)
{
gotoxy(left,21);
cprintf("Nie mo
Ŝ
na drukowa
ć
do %s",destination);
gotoxy(left,22);
cprintf("Wci
ś
nij ENTER, aby kontynuowa
ć
");
bioskey(0);
gotoxy(left,21);
cprintf("%78c",32);
gotoxy(left,22);
cprintf(" ");
}
/* Zlokalizuj pocz
ą
tek pliku */
lseek(handle,0,SEEK_SET);
do
{
/* Załaduj rekord do pami
ę
ci */
result = read(handle,&buffer,recsize);
if (result > 0)
{
ok = 1;
/* Przeszukaj rekord */
if (*rec.name)
if (stricmp(buffer.name,rec.name))
ok = 0;
if (*rec.company)
if (stricmp(buffer.company,rec.company))
ok = 0;
if (*rec.address)
if (stricmp(buffer.address,rec.address))
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
311
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
311
ok = 0;
if (*rec.area)
if (stricmp(buffer.area,rec.area))
ok = 0;
if (*rec.town)
if (stricmp(buffer.town,rec.town))
ok = 0;
if (*rec.county)
if (stricmp(buffer.county,rec.county))
ok = 0;
if (*rec.post)
if (stricmp(buffer.post,rec.post))
ok = 0;
if (*rec.telephone)
if (stricmp(buffer.telephone,rec.telephone))
ok = 0;
if (*rec.fax)
if (stricmp(buffer.fax,rec.fax))
ok = 0;
if (ok)
{
fprintf(fp,"\"%s\",",buffer.name);
fprintf(fp,"\"%s\",",buffer.company);
fprintf(fp,"\"%s\",",buffer.address);
fprintf(fp,"\"%s\",",buffer.area);
fprintf(fp,"\"%s\",",buffer.town);
fprintf(fp,"\"%s\",",buffer.county);
fprintf(fp,"\"%s\",",buffer.post);
fprintf(fp,"\"%s\",",buffer.telephone);
fprintf(fp,"\"%s\"\n",buffer.fax);
}
}
}
while(result > 0);
fclose(fp);
lseek(handle,0,SEEK_SET);
read(handle,&rec,recsize);
DISPDATA();
}
void MENU()
{
int option;
long result;
long end;
int new;
do
{
cursor(21,26);
print("Wybierz polecenie (F2 - F10)");
cursor(7,52);
print("F2 Nast
ę
pny rekord");
cursor(8,52);
print("F3 Poprzedni rekord");
cursor(9,52);
print("F4 Popraw rekord");
cursor(10,52);
print("F5 Dodaj nowy rekord");
cursor(11,52);
312
Hack Wars. Tom 1. Na tropie hakerów
312
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
print("F6 Szukaj");
cursor(12,52);
print("F7 Szukaj dalej");
cursor(13,52);
print("F8 Drukuj etykiety adresowe");
cursor(14,52);
print("F9 Eksportuj rekordy");
cursor(15,52);
print("F10 Wyj
ś
cie");
MOUSE_CURSOR(1);
option = GETOPT();
MOUSE_CURSOR(0);
switch(option)
{
case 0 : /* Nast
ę
pny rekord */
result = read(handle,&rec,recsize);
if (!result)
{
lseek(handle,0,SEEK_SET);
result = read(handle,&rec,recsize);
}
DISPDATA();
break;
case 1 : /* Poprzedni rekord */
result = lseek(handle,0 - recsize * 2,SEEK_CUR);
if (result <= -1)
lseek(handle,0 - recsize,SEEK_END);
result = read(handle,&rec,recsize);
DISPDATA();
break;
case 2 : /* Popraw bie
Ŝą
cy rekord */
new = 1;
if (*rec.name)
new = 0;
else
if (*rec.company)
new = 0;
else
if (*rec.address)
new = 0;
else
if (*rec.area)
new = 0;
else
if (*rec.town)
new = 0;
else
if (*rec.county)
new = 0;
else
if (*rec.post)
new = 0;
else
if (*rec.telephone)
new = 0;
else
if (*rec.fax)
new = 0;
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
313
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
313
result = tell(handle);
lseek(handle,0,SEEK_END);
end = tell(handle);
/* Powrót do pozycji pocz
ą
tkowej */
lseek(handle,result,SEEK_SET);
/* Je
Ŝ
eli nie EOF && !new przesu
ń
o jeden rekord */
if (result != end || ! new)
result = lseek(handle,0 - recsize,SEEK_CUR);
result = tell(handle);
gotoxy(left + 22,21);
print(" Wprowad
ź
dane adresu ");
GETDATA(0);
if (*rec.name || *rec.company)
result = write(handle,&rec,recsize);
break;
case 3 : /* Dodaj rekord */
lseek(handle,0,SEEK_END);
memset(&rec,0,recsize);
DISPDATA();
case 4 : /* Szukaj */
gotoxy(left + 22,21);
print(" ");
SEARCH();
break;
case 5 : /* Szukaj dalej */
gotoxy(left + 22,21);
print(" ");
CONTINUE();
break;
case 6 : /* Drukuj */
gotoxy(left + 22,21);
print(" ");
PRINT_MULTI();
break;
case 7 : /* Eksportuj */
gotoxy(left + 22,21);
print(" ");
EXPORT_MULTI();
break;
case 8 : /* Wyj
ś
cie */
break;
default: /* Popraw rekord bie
Ŝą
cy */
new = 1;
if (*rec.name)
new = 0;
else
if (*rec.company)
new = 0;
else
if (*rec.address)
new = 0;
else
314
Hack Wars. Tom 1. Na tropie hakerów
314
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
if (*rec.area)
new = 0;
else
if (*rec.town)
new = 0;
else
if (*rec.county)
new = 0;
else
if (*rec.post)
new = 0;
else
if (*rec.telephone)
new = 0;
else
if (*rec.fax)
new = 0;
result = tell(handle);
lseek(handle,0,SEEK_END);
end = tell(handle);
/* Powrót do pozycji pocz
ą
tkowej */
lseek(handle,result,SEEK_SET);
/* Je
Ŝ
eli nie EOF && !new przesu
ń
o jeden rekord */
if (result != end || ! new)
result = lseek(handle,0 - recsize,SEEK_CUR);
result = tell(handle);
gotoxy(left + 22,21);
print(" Wprowad
ź
dane adresowe ");
GETDATA(option - 17);
if (*rec.name || *rec.company)
result = write(handle,&rec,recsize);
option = -1;
break;
}
}
while(option != 8);
}
void exec()
{
gettext(1,1,80,25,scr);
setvideo(3);
textbackground(WHITE);
textcolor(BLACK);
clrscr();
recsize = sizeof(data);
OPENDATA();
TRUESHADE(left,3,79,5);
window(left - 2,2 ,78, 4);
textcolor(YELLOW);
textbackground(MAGENTA);
clrscr();
DBOX(left - 3, 1, 77, 3);
gotoxy(3,2);
Rozdział 6.
♦
♦
♦
♦
Podstawy programowania dla hakerów
315
D:\KISIU\PDFy\Chudy\KsiąŜki\Hack_Wars_Tom_1\Hack_Wars_Tom_1\06.doc
315
print("Servile Software PC ADDRESS BOOK 5.2 (c)
1994");
TRUESHADE(left,8,left + 43,18);
window(left - 2,7 , left + 42, 17);
textcolor(BLACK);
textbackground(GREEN);
clrscr();
DBOX(left - 3, 6, left + 41, 16);
TRUESHADE(left + 48,8,79,18);
window(left + 46, 7 , 78, 17);
textbackground(BLUE);
textcolor(YELLOW);
clrscr();
DBOX(left + 45,6,77,16);
TRUESHADE(left ,21,79,24);
window(left - 2, 20 , 78, 23);
textbackground(RED);
textcolor(WHITE);
clrscr();
DBOX(left - 3,19,77,22);
window(1,1,80,25);
textcolor(BLACK);
textbackground(GREEN);
DISPDATA();
MENU();
CLOSEDATA();
puttext(1,1,80,25,scr);
return;
}