Obligement - L'Amiga au maximum

Mardi 12 décembre 2017 - 07:23  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


Programmation : C/assembleur - Exec et la programmation orienté objet : création d'un module-bibliothèque
(Article écrit par Gilles Dridi et prévu pour parution dans Amiga News - décembre 1996)


Préambule

Aujourd'hui, nous allons créer une bibliothèque avec les fonctions de gestion de base qui se chargera en mémoire à partir du disque (répertoire Libs:) ; pour cela on définit un fichier (disque) résident, d'où le nom de module-bibliothèque. Le terme Amiga, courant, "library" en anglais signifie les deux, les deux à la fois. L'assembleur, le compilateur et l'éditeur de lien du SAS-Lattice C ont été utilisés.

Si quelqu'un utilise GCC qu'il rentre en contact avec moi et nous publierons l'assembleur et les ordres pour GCC, car il ne faut pas faire appel à la ixemul.library (et je n'ai pas encore vu comment).

Rappel des instructions µP68000

Vous devez savoir que "dc.b" signifie : "declare byte" (un octet de 8 bits), "dc.w" signifie : "declare word" (un mot de 16 bits) et "dc.l" : "declare long" (un mot de 32 bits). L'instruction JMP signifie "sauter" et l'arobase veut dire que l'on prend l'adresse (pas la valeur à cette adresse) : c'est un branchement inconditionnel.

Les autres instructions à connaître sont "moveQuick" littéralement déplacer, mettre ici le Quick qui signifie prendre la valeur qui est encodée dans le codet d'instruction lui-même. RTS "ReTurn from Subroutine" marche avec JSR "Jump to SubRoutine" enfin "bchg" signifie BitChange et sert à modifier le bit n°1 à l'adresse $bfe001, qui modifie la LED, donc à proscrire si vous n'êtes pas en architecture matérielle native Amiga.

"tst.w" c'est un test de la valeur, ici d'une adresse définie par une étiquette, qui fixe le bit Zero du registre d'état SR et est utile à l'instruction de branchement conditionnel "bne" : BranchifNotEqual.

Enfin, particularité des architectures 68k : CNOP aligne les structures de données sur un mutliple de 4 (long mot). L'adressage minimale en architecture processeur 68k, c'est-à-dire qui ne détériore pas la vitesse du bus, est d'un mot (multiple de 2). Enfin, a0-a7 sont les noms des registres d'adresse donc move.l #_lib,a1 charge l'adresse de l'étiquette "_lib" dans a1 mais move.l $4,a6 va charger la valeur qui se trouve à l'adresse $00000004 de la mémoire :

$00000000 : 35363758
$00000004 : 25140000   (ici)
....
$25140000 : 7001   (là) disons qu'il s'agit du codet MOVEQuick #1, d0 (registre de donnée n°0)
$25140002 : ... suite du code des instructions système

(les instructions CISC du 68k sont d'un mot de 16 bits au minimum)

Vous remarquerez que la lecture en mémoire est toujours un jeu de yoyo avec la dualité adresse : sachant que la valeur peut être une instruction ou une valeur d'adresse elle-même ici. Il vous suffit maintenant de compléter vos connaissances 68k avec les modes d'adressage, au fur et à mesure. ;-)

Structure d'une bibliothèque

Structure

La voici :

Bibliothèque:
Bibliothèque_noeud:
	dc.l	Successeur
	dc.l	Prédécent
	dc.b	Type=Bibliothèque
	dc.b	Priorité
	dc.l	Nom="nom"
Bibliothèque_donnée:
	dc.b	Drapeaux
	dc.b	Rien
	dc.w	TaillePositive
	dc.w	TailleNégative
	dc.w	Version
	dc.w	Révision
	dc.l	Identification="nom version date"
	dc.l	SommeDeContrôle
	dc.l	NombreDUtilisateur

La structure comporte en en-tête un noeud (Successeur, Précédent) car elle va être chaînée dans la liste-système des bibliothèques. Le champ Nom doit être fixé car il est utilisé par la fonction FindName() lors de la recherche d'une bibliothèque. Dans drapeaux, on fixe un bit pour savoir si le champ SommeDeContrôle est utilisé. Enfin, _la base de la bibliothèque_ est l'adresse de cette structure retournée par OpenLibrary(). Au-dessus de cette structure, on trouve les sauts pour les fonctions de la bibliothèque sous la forme :

Saut:
	dc.w	JMP=$4EF9
	dc.l	adresseFonction

Avant de pouvoir coder ses fonctions, il existe quatre fonctions de base.

Les quatre fonctions de base

Ce sont : ouvre, ferme, épure, nulle. On les trouve au-dessus de la bibliothèque. Voici l'organisation :

	dc.w	JMP
	dc.l	@nulle
	dc.w	JMP
	dc.l	@épure
	dc.w	JMP
	dc.l	@ferme
	dc.w	JMP
	dc.l	ouvre
Bibliothèque: ... (la structure bibliothèque)

Par conséquent la première fonction f1() que l'on écrira se trouvera au-dessus de la façon suivante :

	dc.w	JMP
	dc.l	@f1
	dc.w	JMP
	dc.l	@nulle
	... (la suite)

La fonction ouvre() incrémente le champ NombreDUtilisateur, ferme la décrémente. La fonction épure() enlève de la liste système la bibliothèque et renvoie l'adresse du code à décharger. Dans notre cas, notre code aura été chargé par AmigaDOS. La fonction nulle() renvoie NUL (ou 0).

Les appels des quatre fonctions de base par le système

La fonction ouvre() est appelée par OpenLibrary() une fois que celle-ci a parcouru la liste système et a trouvé notre bibliothèque. ferme() est appelée par CloseLibrary(). épure() est appelée par RemLibrary() et c'est épure() qui enlève la bibliothèque de la liste système ; voir explication sur épure().

Structure d'un module résident

Voici la structure aussi appelée ROMtag :

Résident:
	dc.w	ILLEGAL=$4AFC
	dc.l	@Résident
	dc.l	PoursuiteRecherche
	dc.b	Drapeaux
	dc.b	Version
	dc.b	Type
	dc.b	Priorité
	dc.l	Nom="nom"
	dc.l	Identification="nom version date"
	dc.l	Init=@fonction d'init|@Structure d'init

ILLEGAL est une instruction machine qui déclenche une exception de type ILLEGAL. Le champ suivant pointe sur le début de la structure. PoursuiteRecherche doit servir à continuer la recherche de modules résidents en mémoire (on peut simplement le faire pointer sur la fin de la structure). Dans Drapeaux, on peut fixer un bit pour spécifier si le champ Init contient l'adresse d'une fonction à exécuter ou l'adresse d'une structure particulière pour initialisée une bibliothèque. Si Drapeaux n'a aucun bit fixé (0) c'est une fonction qui sera exécutée. Enfin, le champ Nom doit contenir le même nom que le module dans lequel il sera défini, si on place le module sur le disque c'est notre cas. Sinon ce champ est utlisé par FindResident() et est aussi important que dans le cas d'un FindName().

Assemblage d'un module résident

On définit à partir du langage machine une structure Resident :

VERSION	equ	1
PRI		equ	-120

	XREF	_main
	XDEF	_libinit

	SECTION	text,CODE	; romtag doit être dans le premier hunk

	CNOP	4
	
	moveq	#-1,d0
	rts
_Resident:
	dc.w	$4AFC	; instruction ILLEGAL
	dc.l	_Resident 	; validité; pointe sur $4AFC
	dc.l	suiteRecherche	; pour être sérieux 
	dc.b	0	
	dc.b	VERSION
	dc.b	0
	dc.b	PRI
	dc.l	_nom	; ici le nom
	dc.l	0
	dc.l	_main	
suiteRecherche:

	CNOP	4
_nom:
	dc.b	"mod",0
	END

On pourra avoir un programme C nommé test.c assemblé avec ce programme nommé resent.o. Puis on les compile :

asm resent.a
lc -v test.c (-c pas de vérification de pile)

Pour l'édition de lien, il faut spécifier en premier l'objet resent.o :

blink resent.o+test.o to libs:mod

Maintenant, on peut examiner par la commande type libs:mod HEX la structure sur disque du module (ou avec un chargeur/désassembleur). Lzq premières instructions (moveq #-1,d0 / rts) permettent lorsque libs:mod est exécuté à partir du Shell de retourner à AmigaDOS.

Fonctionnement du module à partir du disque

Lorsqu'un utilisateur va ouvrir la bibliothèque de nom "mod", Exec va chercher celle-ci dans la liste système sans la trouver, puis un processus d'AmigaDOS : "ramlib" va charger un module de nom "mod" à partir du répertoire Libs: (Devs: pour OpenDevice()). Après être chargé en mémoire, "ramlib" va vérifier que le nom du module est bien "mod" et va appeler InitResident() sur ce module qui lancera la procédure d'initialisation : _main().

Enfin, après exécution de la procédure _main() le module est totalement déchargé. En effet, pour ne pas être déchargé, il aurait fallu que l'appel à OpenLibrary() réussisse et que la fonction d'init renvoie un pointeur sur la base de la structure bibibliothèque, de plus liée à la liste système.

Remarque : au lieu de charger tout de suite le module à partir du disque, "ramlib" cherche un module résident par FindResident(). Cette fonction parcourt la liste système des modules résidents qui commence à l'adresse fournie par la variable KickTagPtr de la bibliothèque d'Exec.

Programmes d'essais module-bibliothèque

resent.a:

VERSION	equ	1
PRI		equ	-120

	XREF	_LVOAddLibrary	; =$fxxx saut (négatif) par rapport à la base de bib.
	XREF	_LVORemove
	XREF	_main
	XDEF	_libinit

	SECTION	text,CODE	; romtag doit être dans le premier hunk

	CNOP	4
	
	moveq	#-1,d0
	rts
_Resident:
	dc.w	$4AFC	; instruction ILLEGAL
	dc.l	_Resident 	; validité; pointe sur $4AFC
	dc.l	suiteRecherche	; pour être sérieux 
	dc.b	0	
	dc.b	VERSION
	dc.b	0
	dc.b	PRI
	dc.l	_nom	; ici le nom
	dc.l	0
	dc.l	_main	
suiteRecherche:	

	CNOP	4

	dc.w	$4ef9 ; nulle
	dc.l	_nulle
	dc.w	$4ef9 ; epure (JMP)
	dc.l	_epure
	dc.w	$4ef9 ; ferme
	dc.l	_ferme
	dc.w	$4ef9 ; ouvre 
	dc.l	_ouvre
_lib:
	dc.l	0
	dc.l	0
	dc.b	9
	dc.b	0
	dc.l	_nom	; et ici le nom
	dc.b	0
	dc.b	0
	dc.w	0
	dc.w	0
	dc.w	VERSION
	dc.w	0
	dc.l	_nom
	dc.l	0
_lib_cpt:
	dc.w	0

_seg:	dc.l	0
	
_libinit:
	move.l	a0,_seg	; sauvegarde des segments module
	move.l	#_lib,a1
	move.l	$4,a6	; on va se mettre dans la liste système
	jsr	_LVOAddLibrary(a6)
	move.l	#_lib,d0	; utiliser pour les appels bib. avec A6=#_lib
	rts
_ouvre:			; appeler par OpenLibrary()
	addq.w	#1,_lib_cpt
	bchg    #1,$bfe001
	rts
_ferme:			; appeler par CloseLibrary()
	subq.w	#1,_lib_cpt
	bsr	_epure	; enlever la bibliothèque de la liste système ?
	rts
_nulle:
	moveq	#0,d0
	rts
_epure:				; appeler par RemLibrary()
;	moveq	#0,d0
;	tst.w	_lib_cpt
;	bne	1$
	move.l  a6,a1
	move.l  $4,a6	; adresse de la base de la bibliothèque Exec
	jsr     _LVORemove(a6)
	move.l	_seg,d0	; déchargement des segments du module
1$: 	rts

	CNOP	4	
_nom:	dc.b	"mod",0
	END

test.c :

void main();

void main() {
	libinit();
}

ouvre.c :

#include 

void main(void) {
	char *a = "coucou";
	printf("%lx\n", OpenLibrary("mod", 0));
}

epure.c :

#include 

void main(void) {
	long	l;
	char a;
	if ( l= OpenLibrary("mod", 0) ) {
		printf("%lx\n", l);
		a = getchar();
		RemLibrary(l);
	}
}

Pour que la bibliothèque soit effectivement déchargée et enlever de la liste système lorsqu'on tape epure sous Shell, la fonction _epure() de resent.a ne tient pas compte du nombre d'utilisateur de la bibliothèque [télécharger le module-bibliothèque déjà compilé].


[Retour en haut] / [Retour aux articles]