Jak pisać programy w języku asembler pod Linuksem?

Część 11 - Pamięć jest nietrwała, czyli jak posługiwać się plikami

Jak wiemy, wszystkich danych nie zmieścimy w pamięci. A nawet jeśli zmieścimy, to pozostaną tam tylko do najbliższego wyłączenia prądu. Dlatego trzeba je zapisywać do pliku, a potem umieć je z tego pliku odczytać. W tej części zajmiemy się właśnie operacjami na plikach.

Do operowania na plikach posłużymy się kilkoma funkcjami przerwania 80h:

Błędy (podobnie jak w innych funkcjach Linuksowych) są zwykle sygnalizowane przez EAX mniejsze od zera.
Po szczegóły odsyłam do mojego spisu funkcji systemowych, linuxasembly.org, www.lxhp.in-berlin.de/lhpsyscal.html oraz do stron manuala dotyczących poszczególnych funkcji, na przykład man 2 open.

Przykładowe użycie tych funkcji:


(przeskocz przykłady)

Utworzenie pliku i zapisanie czegoś do niego:

	mov	eax, 8		; numer funkcji - tworzenie pliku
	mov	ebx, nazwa	; adres nazwy pliku
	mov	edx, 111111111b	; tryb otwierania - ósemkowo 777
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

	mov	ebx, eax	; EBX = deskryptor pliku

	mov	eax, 4		; numer funkcji - zapis
				; EBX = deskryptor pliku
	mov	ecx, bufor	; adres bufora
	mov	edx, 1024	; liczba bajtów
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

	mov	eax, 6		; numer funkcji - zamknij
				; EBX = deskryptor pliku
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

Otwarcie istniejącego pliku, odczytanie i zapisanie czegoś do niego:

	mov	eax, 5		; numer funkcji - otwieranie pliku
	mov	ebx, nazwa	; adres nazwy pliku
	mov	ecx, 2		; zapis i odczyt
	mov	edx, 111111111b	; tryb otwierania - ósemkowo 777
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

	mov	ebx, eax	; EBX = deskryptor pliku

	mov	eax, 3		; numer funkcji - odczyt
				; EBX = deskryptor pliku
	mov	ecx, bufor	; adres bufora
	mov	edx, 1024	; liczba bajtów
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

	; .... operacje na bajtach z pliku, na przykład
	xor	byte [bufor], 0ffh

	mov	eax, 4		; numer funkcji - zapis
				; EBX = deskryptor pliku
	mov	ecx, bufor	; adres bufora
	mov	edx, 1024	; liczba bajtów
	int	80h

	; Zauważcie, że zapisane bajty wylądowały po odczytanych, gdyż nie
	; zmieniliśmy pozycji w pliku, a ostatnia operacja (odczyt) zostawiła
	; ją tuż po odczytanych bajtach

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

	mov	eax, 6		; numer funkcji - zamknij
				; EBX = deskryptor pliku
	int	80h

	cmp	eax, 0
	jl	blad		; czy wystąpił błąd?

A teraz prawdziwy przykład. Będzie to nieco uszczuplona (pominąłem wczytywanie nazwy pliku) wersja mojego programu na_male.asm. Program ten zamienia wszystkie wielkie litery w podanym pliku na ich małe odpowiedniki. Reszta znaków pozostaje bez zmian. Jedna rzecz jest warta uwagi - nigdzie nie zmieniam rejestru EBX, więc ciągle w nim jest deskryptor pliku i nie muszę tego uchwytu zapisywać do pamięci. A teraz kod:


(przeskocz na_male.asm)
; Program zamienia wszystkie litery w podanym pliku z wielkich na male.
;
; Autor: Bogdan D.
; kontakt: bogdandr (at) op (dot) pl
;
; nasm -O999 -f elf na_male.asm
; ld -s -o na_male na_male.o

section .text

global _start

_start:
	mov	eax, 4
	mov	ebx, 1
	mov	ecx, info
	mov	edx, info_dl
	int	80h		; wypisanie informacji o programie

	mov	eax, 5
	mov	ebx, plik
	mov	ecx, 2
	mov	edx, 111000000b	; 700 - zabroń innym dostępu
	int	80h

	cmp	eax, 0

	jnl	otw_ok
	call	plik_blad	; uruchamiamy tę procedurę,
				; gdy wystąpił błąd

	jmp	zamk_ok		; jeśli nie udało się nam nawet
				; otworzyć pliku, to od razu
				; wychodzimy z programu.


otw_ok:
	mov	ebx, eax	; zapisujemy deskryptor pliku
	mov	ebp, 400h	; EBP = rozmiar bufora.

czytaj:
	mov	eax, 3		; funkcja czytania
				; EBX = deskryptor
	mov	ecx, bufor	; adres bufora, dokąd czytamy
	mov	edx, ebp
	int	80h

czyt_ok:
	xor	edi, edi	; EDI będzie wskaźnikiem do bufora.
				; Na początku go zerujemy.

	cmp	eax, edx	; czy liczba bajtów odczytana (EAX) =
				; = liczba żądana (EDX) ?
	jne	przy_eof	; jeśli nie, to plik się skończył

zamiana:
	mov	dl, [bufor+edi]	; wczytujemy znak z bufora do DL

	cmp	dl, "A"
	jb	znak_ok
	cmp	dl, "Z"
	ja	znak_ok

	or	dl, 20h		; jeśli okazał się wielką literą,
				; zamieniamy go na małą
	mov	[bufor+edi],dl	; i zapisujemy w miejsce,
				; gdzie poprzednio był

znak_ok:
	inc	edi		; przechodzimy do innych znaków
	loop	zamiana		; aż przejdziemy przez cały bufor
				; (CX = BP = 400h)

	mov	ecx, eax	; ECX = liczba przeczytanych bajtów

	mov	eax, 19		; funkcja przejścia do innej
				; pozycji w pliku
				; EBX = deskryptor
	neg	ecx		; ECX = - liczba przeczytanych bajtów
	mov	edx, 1		; wyruszamy z bieżącej pozycji
	int	80h

	cmp	eax, 0
	jnl	idz_ok
	call	plik_blad

idz_ok:				; po udanym przeskoku

	mov	eax, 4		; funkcja zapisu do pliku
				; EBX = deskryptor
	mov	ecx, bufor
	mov	edx, ebp	; EDX = EBP = 400h = długość bufora.
	int	80h

	cmp	eax, 0
	jg	czytaj		; i idziemy czytać nową partię danych
				; (jeśli nie ma błędu)

	call	plik_blad

	jmp	zamk

przy_eof:			; gdy jesteśmy już przy końcu pliku.

;	xor	edi, edi	; EDI już = 0 (zrobione wcześniej)

	mov	ebp, eax	; EBP = liczba przeczytanych znaków
	mov	ecx, eax	; ECX = liczba przeczytanych znaków

zamiana2:
	mov	dl, [bufor+edi]	; pobieramy znak z bufora do DL

	cmp	dl, "A"
	jb	znak_ok2
	cmp	dl, "Z"
	ja	znak_ok2

	or	dl,20h		; jeśli okazał się wielką literą,
				; zamieniamy go na małą
	mov	[bufor+edi],dl	; i zapisujemy w miejsce,
				; gdzie poprzednio był

znak_ok2:
	inc	edi		; przechodzimy do innych znaków
	loop	zamiana2	; aż przejdziemy przez cały bufor
				; (CX = BP = liczba bajtów)

	mov	ecx, eax	; EDX = liczba przeczytanych bajtów

	mov	eax, 19		; funkcja przejścia do innej
				; pozycji w pliku
				; EBX = deskryptor
	neg	ecx		; ECX = - liczba przeczytanych bajtów
	mov	edx, 1		; wyruszamy z bieżącej pozycji
	int	80h

	cmp	eax, 0
	jnl	idz_ok2
	call	plik_blad

idz_ok2:			; po udanym przeskoku

	mov	eax, 4		; funkcja zapisu do pliku
				; EBX = deskryptor
	mov	ecx, bufor
	mov	edx, ebp	; EDX=EBP=liczba przeczytanych bajtów
	int	80h

	cmp	eax, 0
	jnl	zamk		; i zamykamy plik (jeśli nie ma błędu)

	call	plik_blad

zamk:
	mov	eax, 6		; zamykamy plik
				; EBX = deskryptor
	int	80h


zamk_ok:
	mov	eax, 1
	xor	ebx, ebx
	int	80h


plik_blad:			; procedura wyświetla informację
				; o tym, że wystąpił błąd i
				; wypisuje numer tego błędu.
	push	eax
	push	ebx
	push	ecx
	push	edx
	push	ebx

	mov	eax, 4
	mov	ebx, 1
	mov	ecx, blad_plik
	mov	edx, blad_plik_dl
	int	80h		; wypisanie informacji o tym,
				; że wystąpił błąd

	pop	ebx
	call	pl		; wypisanie numeru błędu

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, nwln
        mov     edx, 1
        int     80h		; przejście do nowej linii

	pop	edx
	pop	ecx
	pop	ebx
	pop	eax

	ret

pl:

piszrej:

;we: ebx - rejestr do wypisania (hex)
;wy: rejestr, niszczone: eax

	mov	eax, ebx
	shr	eax, 28
	call	pc2
	mov	eax, ebx
	shr	eax, 24
	and	al, 0fh
	call	pc2
	mov	eax, ebx
	shr	eax, 20
	and	al, 0fh
	call	pc2
	mov	eax, ebx
	shr	eax, 16
	and	al, 0fh
	call	pc2
	mov	ax, bx
	shr	ax, 12
	and	al, 0fh
	call	pc2
	mov	ax, bx
	shr	ax, 8
	and	al, 0fh
	call	pc2
	mov	al, bl
	shr	al, 4
	and	al, 0fh
	call	pc2
	mov	al, bl
	and	al, 0fh
	call	pc2

	ret

pc2:
;we: AL - cyfra hex
;wy: wyświetla cyfrę, niszczone: nic

	push	eax
	push	ebx
	push	ecx
	push	edx

	cmp	al, 9
	ja	hex
	or	al, "0"
	jmp	short pz
hex:
	add	al, "A"-10

pz:
	mov	[cyfra], al
	mov	eax, 4
	mov	ebx, 1
	mov	ecx, cyfra
	mov	edx, 1
	int	80h

	pop	edx
	pop	ecx
	pop	ebx
	pop	eax

	ret

section .data

align 16
bufor 		times 400h db 0		; bufor wielkości jednego kilobajta
;plik 		times 80 db 0
plik		db "aaa.txt",0		; nazwa pliku

info 	db "Program zamienia wielkie litery w pliku na male.",10
info_dl		equ	$-info

input1		db "Podaj nazwe pliku do przetworzenia: "
input1_dl	equ	$-input1

zla_nazwa 	db 10, "Zla nazwa pliku."
zla_nazwa_dl	equ	$-zla_nazwa

blad_plik 	db 10,"Blad operacji na pliku. Kod: "
blad_plik_dl	equ	$-blad_plik

cyfra		db 0

Ten program chyba nie był za trudny, prawda? Cała treść skupia się na odczytaniu paczki bajtów, ewentualnej ich podmianie i zapisaniu ich w to samo miejsce, gdzie były wcześniej.

Pliki są podstawowym sposobem przechowywania danych. Myślę więc, że się ze mną zgodzicie, iż opanowanie ich obsługi jest ważne i nie jest to aż tak trudne, jakby się mogło wydawać.



Poprzednia część kursu (klawisz dostępu 3)
Kolejna część kursu (klawisz dostępu 4)
Spis treści off-line (klawisz dostępu 1)
Spis treści on-line (klawisz dostępu 2)
Ułatwienia dla niepełnosprawnych (klawisz dostępu 0)



Ćwiczenia

  1. Napisz program, który wykona po kolei następujące czynności:
    1. Utworzy nowy plik
    2. Zapisze do niego 256 bajtów o wartościach od 00 do FF (nie musicie zapisywać po 1 bajcie)
    3. Zamknie ten plik
    4. Otworzy ponownie ten sam plik
    5. Zapisze odczytane bajty w nowej tablicy 256 słów w taki sposób:
      		00 00 00 01 00 02 00 03 00 04 .... 00 FD 00 FE 00 FF
      czyli każdy oddzielony bajtem zerowym (należy przeczytać wszystkie bajty, po czym ręcznie je przenieść gdzie indziej i wzbogacić)
    6. Zamknie otwarty plik
    7. Usunie ten plik