MSX Village forum

La Place des Développeurs Le développement avec un cross-assembler

Visiteur

Vagabond

Rang

Avatar

Message : 0

Le 24/08/2010 à 16h49
Ce texte est passé à travers mon filtre de conversion dotclear / BBCode. Ce filtre étant hautement expérimental, vous voudrez bien excuser les quelques typos qui subsistent, merci :oups



Cross-assembler ?



Le principe est d'utiliser un compilateur croisé (cross assembler) sur votre système hôte. Les fichiers exécutables sont fournis à l'émulateur ou à la machine ciblée.

Ceci permet d'automatiser au maximum toutes les manipulations :

Le cycle édition / compilation / test / debuggage doit se faire naturellement, et ne pas être soumis à des manipulations continuelles.



Voici les résultat de plusieurs jours d'expérimentations.

J'ai volontairement omis les cross-assemblers qui me paraissaient vraiment morts (ex: ZASM).

Je ne parle pas non plus de tniASM, qui semble intéressant, mais uniquement disponible sous MS-Windows (et pas open source).

Merci de me signaler les oublis.











Choix du cross-assembler Z80





ASmsx



Je commence par celui dont je n'ai pas trouvé le site officiel.

Les sources ne semblent pas disponibles, du moins dans les archives que j'ai récupéré.

Il semble même ne pas y avoir de version autre que MS-Windows.

Il permet de réaliser facilement des ROMs avec les différents mappers MSX.











Pasmo



Pasmo semble déconseillé.

Il comporte quelques bizarreries, notamment la nécessité de commencer le code par la directive «org». Ce qui donne «org $C000-7» pour sauter le stub qui vient ensuite.

Mais certains projets l'utilisent, comme le C-BIOS.

J'ai discuté avec quelques personnes gravitant autour du projet C-BIOS, ça n'a pas l'air d'être le fol enthousiasme. Plutôt un choix par défaut.



Je n'ai donc pas évalué Pasmo, certain de trouver mieux ailleurs.

La suite de mes recherches me laisse à penser que je l'ai peut être éliminé un peu vite.











Sjasm




Grandeur et décadence de Sjasm




Sjasm semble jouir d'un crédit assez étonnant dans la communauté msx.

En fait, les anciennes versions étaient fonctionnelles, mais les plus récentes sont une vraie catastrophe.

Je vais développer, pour ne pas être accusé de taper sans raison sur un bon produit, ou de ne rien comprendre à la programmation.

Sjasm peut s'utiliser à partir d'un binaire (qui semble fonctionner), ou a partir des sources.

Je préfère compiler quand les sources sont disponibles, cela donne souvent de meilleurs résultats.

Il s'agit ici de la version 0.42b8






Compilation de Sjasm 0.42b8




make donne des erreurs (je passe charitablement sur les warnings), notamment:

datastructures.h:50: erreur: ?memcpy? was not declared in this scope

(erreur répétée plusieurs fois dans ce même entête)



Explication: «memcpy» est considéré comme une fonction membre de «Data».

Data(byte *n_data,int n_size) : _data(0), _size(n_size), _maxsize(n_size)

{ _resize(n_size); memcpy(_data,n_data,n_size); }




Ce n'est bien sûr pas le cas. Habituellement, «memcpy» est déclaré dans «string.h».

Faut-il faire un include de ce fichier au début de l'entête fautif ?

memcpy n'est utilisé que dans «datastructures.h», ainsi qu'un appel dans «datastructures.cpp».

voici son prototype: void *memcpy (void *dest, const void *src, size_t n);

la véritable fonction «memcpy» renvoie un pointeur sur *dest



J'ai ajouté memcpy dans la classe «Data» (au début avant même le constructeur, dans les membres «public» bien sûr) :

void *memcpy (byte *dest, byte *src, size_t n);

et je l'ai défini ainsi dans datastructures.cpp:



Code :


void * Data::memcpy (byte *dest, byte *src, size_t n)
{
  for (int i=0; i<n; i++) dest[i]=src[i];
  return dest;
}









Toujours dans datastructures.c, on a ensuite une référence inconnue à memset (également déclaré dans «string.h»)

void *memset (void *s, int c, size_t n);

comprendre: rempli *s 'n' fois avec c

C'était la seule référence. J'ai remplacé par une simple boucle.



Code :


//memset(_data+_size,0,nsize-_size);
 for (int i=0; i<nsize-_size; i++) _data[_size+i]=0;







Erreur suivante:

errors.cpp:58: erreur: ?exit? was not declared in this scope

toujours un include à ajouter:

#include <cstdlib> (et non pas «stdlib.h») dans «errors.cpp» (après le include "sjasm.h")



Et maintenant, il manque «stdio»

fileio.cpp:37: erreur: ?fopen? was not declared in this scope

(puis ensuite fclose, fseek, ftell, fwrite. En gros, tout la bande des I/O de stdio.h). On se demande à quoi sert de faire du C++ si c'est pour reprendre toute la librairie standard du C.

On inclut «#include <cstdio>» (et non pas «stdio.h») juste après le include «sjasm.h» au début de fileio.cpp.



Erreur suivante: valeur absolue

rawsource.cpp:449: erreur: ?abs? was not declared in this scope

prototype dans «stdlib.h» : int abs (int j);

Ca calcule une valeur absolue. Bon, on va le faire nous même.



Code :


  int absolute=_offset%al;
  if (absolute<0) absolute=-absolute;
  if (_offset%al) _offset+=al-absolute; //abs(_offset%al);









Tiens c'est mignon, ça: reader.cpp:73: erreur: ?malloc? was not declared in this scope

effectivement, il y a bien un malloc t: if (b.length()+1>64) bp=p=(char*)malloc(b.length()+1);

Bon un «free» doit trainer quelque part, au milieu des «delete».

Vérifions tout de suite (grep free *). Ah tiens... non. memory leak en perspective ?

#include <cstdlib> , après le #include "sjasm.h" (la routine quoi)



Allez , c'est le dernier:



Code :


sjasm.cpp:134: erreur: ?exit? was not declared in this scope
sjasm.cpp:145: erreur: ?exit? was not declared in this scope





Allez, include cstdlib again.





Et voila. L'exécutable __sjasm__ est construit.



Il compile bien au moins ? Petit test...



Ca a l'air de fonctionner.






Patch pour Sjasm 0.42b8




J'ai donc écrit un patch que vous pourrez appliquer directement sur les sources du 0.42b8 si le coeur vous en dit.



diff -urNb sjasmsrc42b8 sjasmsrc42b8.jseb >sjasm42b8.patch

Le résultat est un peu long pour être publié ici, j'en ai fait une archive, et je l'ai uploadé dans la partie fichiers du site.



Les instructions pour appliquer le patch se trouve dans l'archive.



Une fois les fichiers patchés, vous pouvez lancer make (ne faites pas attention aux warnings, je n'ai pas corrigé cette partie).









Sjasmplus



Voyons voir Sjasmplus, qui est un fork d'une ancienne version de Sjasm.

Houlala, sur le site officiel, il y a un lien pour les «recommended text editors».

Ok, ni Vim, ni Emacs, ni même Ultraedit. Ca commence bien. Je n'ai même pas envie de reprendre les sources pour tout dire.



Allez, courage.

Bon alors, il y a une stable, et une version de dev. La version de dev n'inclue pas le scripting avec Lua.

Je reprends la stable, on verra la version de dev plus tard, éventuellement.



La version stable est la 1.06 (3 aout 2006)



Je lance la compilation.

A première vue, autant de warnings que chez son petit frère Sjasm.

Ah, et voici les premières erreurs:



Code :


Sjasm.cpp:167: erreur: ?MAXPATHLEN? was not declared in this scope
Sjasm.cpp:199: erreur: ?zoekpad? was not declared in this scope





Là j'avoue, je n'ai pas trop cherché.

car c'est alors que je suis tombé sur ça :



Code :


#ifdef WIN32
int main(int argc, char *argv[]) {
#else
int main(int argc, char **argv) {





Il se faisait tard, j'ai laissé tomber.



Je me demande à quoi pensait le type qui a écrit ça.











z80-asm





z80-asm, avec un tiret dans le titre

Je précise pour le tiret, car il a un homonyme sans tiret (il sera le prochain abordé).



z80-asm semble complet : il inclut même un moniteur/debugger «interactif» à l'ancienne (dans un terminal avec interface façon ncurses).

http://wwwhomes.uni-bielefeld.de/achim/z80-asm.html



Le problème est sa rigidité syntaxique.

Il ne veut pas des instructions «db» de mon programme de test.

Il faut remplacer les «db» par des «defb», les «dw» par des «defw», etc...

Les strings ne sont admises qu'avec une déclaration defm (defb ne passera pas).



Pour illustrer ce problème, cette déclaration sera refusée:



Code :


LABEL: defb "Hello",13,10,0





Et il faudra l'écrire comme ceci:



Code :


LABEL: defm 'Hello chput'
       defb 13,10,0 





Attention, pour avoir l'aide, c'est «-h». Et non pas «--help» ou «-help».

Si vous ne précisez pas de fichier de sortie, la syntaxe du programme sera vérifiée, mais vous aurez le message: «No code generated» même si vous n'avez pas d'erreur.



Par défaut, z80-asm rajoute un header qui empêche toute exécution par le msx, et il prend au pied de la lettre la directive «org $c000» en générant un binaire de 48 Ko!



Code :


jseb ~/msx $ hexdump -C test-z80-asm.bin
00000000  5a 38 30 41 53 4d 1a 0a  00 00 fe 00 c0 1b c0 00  |Z80ASM..........|
00000010  c0 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0000c000  00 00 00 00 00 00 00 00  00 00 21 0d c0 7e 23 a7  |..........!..~#.|
0000c010  c8 cd a2 00 18 f7 c9 48  65 6c 6c 6f 20 63 68 70  |.......Hello chp|
0000c020  75 74 0d 0a 00                                    |ut...|
0000c025





Pour les informations sur le header, voir dans la doc: z80-file.5 (ou «man z80-file» si vous avez installé les pages man).

En gros, il faut enlever les 10 premiers octets.



Pour comparaison, le même programme généré avec Sjasm.



Code :


jseb ~/msx $ hexdump -C sjasm.bin
00000000  fe 00 c0 1b c0 00 c0 21  0d c0 7e 23 a7 c8 cd a2  |.......!..~#....|
00000010  00 18 f7 c9 48 65 6c 6c  6f 20 63 68 70 75 74 0d  |....Hello chput.|
00000020  0a 00                                             |..|
00000022





Je n'ai pas trouvé la façon d'enlever l'entête dans le manuel de z80asm.

Du coup, je l'ai fait moi même avec un coup de dd.

dd if=test2.bin of=raw.bin ibs=1 skip=10

Mais ne vous fatiguez même pas à essayer. Le code ne s'exécutera bien sûr pas dans l'émulateur.



Dommage, cet assembleur semblait un bon candidat, abstraction faite de sa syntaxe rigide.

Le package possède également un moniteur, déja évoqué ci-dessus.

Cela dit, rien ne vous empêche d'utiliser le moniteur et de compiler avec un autre cross-assembler.











z80asm



Un bon petit cross-assembler, un peu limité mais à l'approche facile.

Le nom ressemble fortement au précédent, et se différencie par l'absence de tiret (ça devient subtil).

Mais cette fois, nous avons affaire à un produit plus utilisable



Il a été développé par un possesseur de msx: Bas Wijnen (son pseudo est Sheveks)

http://savannah.nongnu.org/projects/z80asm



Bonne surprise, le code source compile sans erreurs ni warnings.



Certaines fonctionnalités sont absentes:

Exemple tiré du manuel: «rlc r,(ix+nn) and friends don't work»

Cela ne me parait pas bien grave, et il y a toujours possibilité de faire une macro pour assembler directement l'instruction en écrivant les octets des instructions avec une séquence db.



Côté directives, c'est le minimum syndical: defb/db, defwdw, ds/defs , include, if/else/endif.

Pas de macros ni de «incbin» pour ajouter directement des fichiers binaires.

Pour «incbin», ce n'est pas bien grave. On peut transformer des données binaires en db/dw, et les inclure avec la directive «include».



La doc est ici: http://www.nongnu.org/z80asm/



Mon programme de test a été assemblé sans problèmes, sans adaptations syntaxiques, et a parfaitement fonctionné sous OpenMSX.

Bien que limité, z80asm semble être un bon choix pour le programmeur pressé de faire ses premiers programmes.









WLA DX : le meilleur pour la fin




Des caractéristiques alléchantes




C'est un projet vivant avec des patches récents (aout 2010).

Ce cross-assembler supporte d'autres processeurs que le Z80 (6502, 6510 ...)

Pour ne rien vous cacher, la désignation complète est «Yet Another GB-Z80/Z80/6502/65C02/6510/65816/HUC6280/SPC-700 Multi Platform Cross Assembler Package».



A noter que la compilation et le link sont séparés, ce qui permet de linker des objets entre eux, ou des libs.

Cette séparation des commandes parait aller de soi, mais elle est absente des cross-assemblers que j'ai testé jusqu'à présent.

Pour plus de détails, référez-vous aux sections «compiling» et «linking» dans le README (section 9 et 10).



Les possibilités offertes par les directives disponibles sont impressionantes.

Attention, il y a quelques pièges que je détaillerai plus loin.



La doc n'est pas toujours très claire, mais elle est complète.

Il ne faudra pas hésiter à faire quelques allers-retours dans la doc. Les informations sont parfois un peu éparpillées.






Compilation de WLA DX




Compilateur et linkers étant séparés, il va falloir procéder en deux temps.



La dernière version sur le site de Ville Helin (son auteur) est la 9.5 (aout 2008).

Les anciennes versions sont sur le site de l'auteur:

http://www.villehelin.com/wla.html



Depuis, il est hébergé sur sourceforge.

Le mieux est de récupérer les derniers commits avec subversion depuis le repository:

svn co https://wladx.svn.sourceforge.net/svnroot/wladx wladx



Il faut ensuite aller dans le répertoire «makefiles», et choisir celui qui nous intéresse.

Dans notre cas, il s'agit bien sûr de «makefile.unix.z80»



Copier ce makefile dans le répertoire des sources du compilateur (répertoire racine "wladx").

Modifiez le pour virer le flag «-O3» et mettre un «-O2» à la place.



make clean && make -f makefile.unix.z80

Le make clean est là au cas où une autre architecture aurait été compilée auparavant.

Ainsi, le define «-DZ80» sera bien pris en compte.



Pas de warnings inquiétants à signaler (n'oublions pas que les flags sont assez durs: «-ansi -pedantic -Wall» ! )

Lancez l'exécutable obtenu (wla-z80).

La première ligne doit indiquer qu'il s'agit de la version Z80 :



Code :


/tmp/wladx$ ./wla-z80 

WLA Z80 Macro Assembler v9.5





Si vous obtenez la ligne ci-dessus, le compilateur est prêt.



Il reste à s'occuper du linker, qui lui est commun à tous les compilateurs supportés par WLA.

Le linker se trouve dans le sous-répertoire «wlalink»



Il faut modifier «wlalink/makefile.unix» sur deux points:

Remplacer -O3 par -O2 (comme pour le compilateur).

Remplacer $(LD) par $(CC) (sinon la compilation plante lors du link sur la libc).



Vous pouvez maintenant faire un:

make clean && make -f makefile.unix

L'exécutable obtenu se nomme «wlalink»



Code :


/tmp/wladx/wlalink$ ./wlalink

WLALINK GB-Z80/Z80/6502/65C02/6510/65816/HUC6280/SPC-700 WLA Macro Assembler Linker v5.7





Et voila, le linker est prêt également.










Test de WLA DX






WLA DX a une approche assez originale qui peut dérouter au premier abord.



Avant toutes choses, lecture (même rapide) obligatoire du README (91ko!) qui fait office de documentation principale.



Voici un programme de démonstration adapté à WLA DX, avec quelques explications.



Code :

; test.asm - for testing WLA cross compiler.
; jseb @ finiderire . com

.MEMORYMAP
SLOTSIZE $FFFF
DEFAULTSLOT 0
SLOT 0 $0
.ENDME

.ROMBANKMAP
BANKSTOTAL 1
BANKSIZE $FFFF
BANKS 1
.ENDRO

.section "BANKHEADER"
  .db $fe
  .dw start,end,start; 
.ends

.BANK 0 SLOT 0
.ORG $C000 

start:
     LD HL,label
aff: LD A,(HL)
     INC HL
     AND A
     RET Z
     CALL $A2 ; CHPUT
     JR aff
     RET

label: .DB "Venom",13,10,0
end: .equ $-1






Au début du programme, vous remarquez quelques directives:

.MEMORYMAP et .ROMBANKMAP

Elles servent à définir la structure des roms. Ici, j'ai fait au plus simple (un seul slot, une seule banque, la totalité de l'espace adressable par un Z80 assigné sur le même slot).



BANKHEADER est plus intéressant, et nécessaire pour écrire le stub de notre programme.

les octets contenus dans BANKHEADER seront simplement ajoutés au début de l'objet, et ne provoqueront pas de décalage dans le calcul d'adressage à la compilation. Dans la doc, il est même précisé que c'est une option très utile pour le MSX (tu m'étonnes).



Le «.BANK 0 SLOT 0» ne semble pas obligatoire, car notre cas est très simple. Mais dans d'autre cas, il peut être utile de spécifier la BANK ou le SLOT. J'ai donc laissé cette directive pour attirer votre attention sur son existence.



A propos de BANK et de SLOT, la terminologie employée ici n'ait aucun rapport avec le memory mapping du MSX , même si les mots sont identiques.



Avant de nous lancer dans la compilation et le link, un gris piège est à signaler:

«.equ» est un alias pour «.define»

c'est à dire: «.define : Assigns a number or a string to a definition label.»

Dans l'exemple en dessous, on met donc start à zéro! Ce n'est pas le comportement habituel qui serait d'assigner la valeur de start à exec.



exec: .equ start ;en réalité, met start à zéro !!



Il semble qu'on ne puisse pas assigner de valeur à une étiquette en utilisant la valeur d'une autre étiquette.

Par contre, on peut faire ça: exec: .equ $C000


Ce qui n'a pas un grand interet (voire même peut être dangereux si on change la valeur de .ORG )







Pour la compilation :

wla-z80 -o test.asm

le flag «-o» sert à demander la génération d'un fichier objet.

le nom du fichier objet est par défaut celui du fichier source avec l'extension «.o»



Pour le link :

wlalink -b test.group test.bin

le flag «-b» est important, car par défaut, c'est «-r» qui est utilisé (ce flag sert à créer une rom).

Le fichier «test.group» contient différents paramètres, dont le nom des fichiers objets à utiliser pour le link (voir la doc).

Ici, le fichier «test.group» contient ceci:



Code :


[objects]
test.o





Il est réduit ici à sa plus simple expression.



Voilà un tour rapide de WLA. Vous en savez assez pour lire la doc et pouvoir m'expliquer comment générer une ROM avec ce compilateur!









Le cas des macro cross-assemblers




macro quoi ?




Un «macro cross assembler» est un cross compilateur classique, mais qui fonctionne avec un système de macros.

Les macros sont des regroupements d' instructions (ici, les mnémoniques), sous une seule instruction afin de faciliter la lecture.

Une macro sert à automatiser l'insertion d'un bloc d'instruction.

Le support par le compilateur des macros peut être plus ou moins étendu:

Cela peut aller du simple remplacement de texte (comme une fonction inline) à l'intégration de la gestion des opérateurs arithmétiques et logiques.



Notez qu'il existe bien sûr des macro assemblers dédiés à la plateforme hôte. Ils ne sont donc pas cross-compilers.



Les macro-assemblers permettent d'introduire un peu de haut niveau dans le monde aride de l'assembleur.

Le revers de la médaille peut être une génération de code bien foireuse, surtout pour des projets amateurs peu suivis.






Le macro cross-assembler AS




AS rentre dans le club très fermé décrit ci-dessus.

Son support des macros est suffisament étendu pour justifier un traitement à part.

Vous le trouverez ici:

http://john.ccac.rwth-aachen.de:8000/as/



Voici un exemple de sa syntaxe



Code :


dbstr   macro   arg
        if      strlen(arg) > 1
         db     substr(arg, 0, strlen(arg) - 1)
        endif
        if      strlen(arg) > 0
         db     charfromstr(arg, strlen(arg) - 1) | 80h
        endif
        endm 





Il m'a l'air ma foi fort intéressant...

mais j'avoue que je commence à me lasser de tous ces tests.









Mes choix de cross-assemblers



Il faut bien choisir, alors voici mes préférés.






WLA DX, le meilleur et le plus exigeant




WLA DX est de loin le meilleur cross compiler que j'ai pu tester. Il y a un monde entre lui, et Sjasm par exemple.



Ses avantages:

Le compilateur et le linker sont séparés, ce qui est une très bonne chose.

Il compile sans warning inquiétants, ni erreurs, avec des flags qui ne pardonnent pourtant pas.

La doc est bien fournie.

Il est maintenu et réellement multi-plateformes, signe d'une bonne conception.

Le code est en C, preuve de bon goût (mieux vaut écrire du C rustique et fonctionnel que du C++ qui compile à peine).

Seule une relative complexité pourrait vous conduire à lui préférer z80asm (celui de Sheveks).



Ses inconvénients:

Une façon de faire aux antipodes de la simplicité.

Une doc pas toujours très claire.

Nécessite surement un peu d'expérience en programmation pour être apprécié.






z80asm, rustique mais efficace




Celui-ci est simple, mais n'a posé de problèmes ni à la compilation, ni à l'utilisation.

D'autres part, il a été développé spécifiquement pour être utilisé avec un MSX.

Cependant, contrairement à ASmsx, il ne permet pas de créer facilement des ROMs.

Seuls quelques fichiers d'équivalences spécifiques aux MSX sont fournis.

Sa rusticité (peu de directives disponibles) ne doit pas faire oublier sa facilité d'emploi.






Sjasm 0.42b8, le pari




Avec Sjasm, vous avez un cross-assembler relativement séduisant sur le papier.

Ses sources font cependant assez peur, et le patch que je vous propose pour le compiler ne corrige que les erreurs bloquants la compilation.

Une fois que j'ai réussi à le compiler, mes programmes de tests sont passés sans problèmes.

A tester donc, pour les plus joueurs d'entre-vous.






Et pour les Windoziens, c'est tniASM




tniASM semble pas mal... pour les utilisateurs de MS-Windows.

On le trouve ici http://tniasm.tni.nl Edité par Visiteur Le 24/08/2010 à 16h51
   
KN2000 Membre non connecté

Villageois

Rang

Avatar

Inscrit le : 08/06/2010 à 09h30

Messages: 209

Le 24/08/2010 à 17h31
Excellent travail de recherche et de tests !!! :glass


Nous sommes en 2010 aps JC, toute la Gaule est envahie. Ah ben non, apparement, un village résiste encore aux envahisseurs Personalcomputerum et autres Consoledessalum. Bienvenue dans l'antre du emessix !
   
Hecatonchire Membre non connecté

Vagabond

Rang

Avatar

Inscrit le : 24/08/2010 à 08h07

Messages: 3

Le 24/08/2010 à 18h27
Very good.
Ca donne envie de s'y remettre.
Se remettre à mon propre cross assembleur je veux dire.
Un jour peut-être, quand j'arriverai à la retraite.
Mais attention mon truc ce sera de la uber killer app.
Décompilateur/recompilateur temps réel.
Le binaire se compile et s'optimise en temps réel dans son propre IDE en même temps que le code est tapé/modifié.
Le tout en Rebol.

Quoi ??

On peut rêver...


   
Répondre
Vous n'êtes pas autorisé à écrire dans cette catégorie