[Tutoriel] Initiation à l'assembleur SuperH
Posté le 30/08/2013 20:14
Je vous présente ce petit tutoriel écrit entre la salle d'entrainement du code de la route, la salle d'attente du médecin et le train. Ne voyez pas ce tuto comme un cours complet pour savoir coder comme un pro en assembleur, pour la simple raison que je ne suis moi-même pas capable de super bien coder en assembleur. Voyez ça plutôt comme une rampe de lancement pour vous donner les bases et éviter de galérer sur des points ou j'ai moi-même galéré.
J'ai appris sur le tas en faisant mon "SH4 compatibility tool", j'ai jamais suivis de cours/tuto, donc il y a de fortes chances que je n'emploie pas les bons termes ou que je dise carrément de bêtises, ne prenez pas ce tuto pour la bible superH. La bible superH, je vais vous la présenter et elle fait 581 pages
Pour pouvoir aborder ce tutoriel, vous devez maitriser le C, très bien maitriser le concept de base numérique et en particulier bien gérer le binaire, le décimale et l'hexadécimal (l'hexadécimal que je noterais avec le préfixe 0x)
Notez que ce tutoriel porte sur l'assembleur superH, celui qui fonctionne avec les processeurs Renesas SuperH et ça n'a rien à voir avec l'assembleur x86 qu'il y a sur votre ordinateur (seuls les concepts de base restent, mais les instructions et tous les outils auxquels nous avons accès changent).
Qu'est ce qu'un programme
Vous êtes vous déjà demandé qu'est-ce que contient un fichier de programme une fois compilé et comment l'ordinateur l'exécute ce fichier ?
Le processeur d'un ordinateur (ou d'une calculatrice) est capable d'exécuter différentes instructions élémentaires. Une instruction est une très petite opération, par exemple "Copier 1 octet depuis cette mémoire vers un autre endroit en mémoire", "ajouter une valeur à une autre" ou encore "Mettre 1 dans un endroit en mémoire si une valeur est supérieure à une autre". Lorsqu'en C, vous faites un "if", il sera représenté par plusieurs instructions.
Un "exécutable" ou chez nous un AddIn est une sorte de liste de ces différentes instructions. Evidemment elles ne sont pas écrites dans un langage facilement lisible par les humains car ça n'est pas destiné aux humains et si c'était compréhensible par un humain ça prendrait beaucoup plus de place. Pour vous donner un ordre d'idées, en assembleur superH la longueur d'une instruction est constante et elle fait 16bits, c'est-à-dire deux octets (équivalant à deux caractères).
Le langage assembleur est en fait une traduction de ces instructions de sortes qu'elles soient lisible par l'homme. Enfin ça ne veut pas dire que c'est compréhensible par le premier venu.
Les adresses et les registres
Les instructions que nous avons vues précédemment manipulent principalement :
- Des
adresses qui représentent selon les cas vont représenter simplement un stockage en mémoire, ou un accès pour lire et écrire sur l’écran, ou encore lire les entrées du clavier.
- Des
registres : ce sont des sortes de variable qui vont nous permettre de stocker temporairement une valeur. Ils peuvent contenir 4 octets et il en existe un nombre limité et défini par le type de processeur. Certains ont un rôle défini et ne devront pas être modifiés sans en comprendre leurs fonctionnements sous peine de faire planter la calculatrice, voire de lui causer des dégâts. Nous utiliserons très souvent les registres généraux : R0, R1, R2,…, R15, mais il en existe d'autres comme PC qui contient la position actuelle dans le fichier.
- Des
flags (drapeaux en français) : Ce sont eux aussi des sortes de variables, sauf qu'ils ne peuvent contenir que 1 ou 0. Eux aussi peuvent avoir un rôle défini et sont en nombre limité et défini par le type de processeur. Nous utiliserons souvent le flag "T" qui contient le résultat des comparaisons entre deux valeurs.
Vive les jump et les labels
Si vous connaissez le basic Casio, vous aurez peut-être entendu qu’on n’aime pas les labels/goto car ça casse le principe d'algorithmes. Ici on peut les utiliser sans se faire insulter par les puristes car c'est tout simplement le seul moyen de passer d'un endroit à un autre. C'est comme ça que fonctionne une machine, lorsqu'on appelle une fonction, il va "jumper"(sauter) au début de la fonction et à la fin il "jump" là où il était avant. Tout fonctionne comme ça. Le langage assembleur étant une sorte de traduction "mot à mot" du langage machine, vous retrouvez le même fonctionnement. C'est, entre autres, pour ça que coder en assembleur n'est pas simple et ne ressemble pas totalement à de l'algorithme à proprement parler.
Les types
Il existe trois types gérés naturellement par le processeur:
- Byte : un octet
- Word : 2 octet
- Longword : 4 octets
On les retrouvera souvent dans les noms d'instructions avec leurs initiales. Par exemple il existe MOV.B, MOV.W et MOV.L.
La première lecture d'un code assembleur
Comme première application de ce que nous avons vu précédemment, nous allons :
- Compiler une fonction en C
- Récupérer le binaire
- Le désassembler manuellement (c'est-à-dire le traduire en langage assembleur)
- En comprendre le fonctionnement.
Compiler une fonction en C
Créez un nouveau projet sur le SDK Casio et en dessous des #incude, vous collez simplement le code suivant, sans chercher à appeler cette fonction quelque part, elle ne sera jamais exécutée et ce n’est pas un problème.
int test()
{
int bob;
bob = 9;
bob++;
return bob;
}
Comme vous le voyez ce code n'a strictement aucun intérêt, mais ce n'est pas grave, le compilateur va quand même le traduire en langage assembleur et c'est ce que l'on veut.
Récupérer le binaire
Nous allons récupérer le contenu de cette fonction sous forme hexadécimale. Pour cela, il va vous falloir un éditeur hexadécimal. C'est-à-dire un éditeur comme Notepad, à la différence qu'il va y avoir une colonne où chaque octet est représenté sous forme hexadécimal et sur la droite vous aurez une deuxième colonne qui vous affiche la traduction de ces octets en caractères afin de repérer des textes s'il y en a, mais cette colonne ne nous sera pas vraiment utile pour l'instant.
Le meilleur éditeur hexadécimal que je connaisse (et croyez-moi, j'en ai testé pas mal) est
HxD (
ici pour télécharger ) même s'il a toujours quelques défaut comme le fait d'être incompatible sur linux (désolés pour les linuxiens ou les macintoshiens, je vous laisse faire une petite recherche sur Google pour trouver votre bonheur, vous en trouverez forcément. Le problème est d'en trouver un bien). Et puis sinon, Wine
Vous allez donc ouvrir le fichier g1a que vous venez de compiler avec votre éditeur hexadécimal. Vous verrez qu'à première vue ce n'est pas très accueillant et que ça risque de ne pas être simple de retrouver notre fonction la dedans.
Heureusement, notre compilateur génère un fichier très pratique lorsqu'il compile : allez dans le dossier de votre projet, puis dans le dossier "Debug" et ouvrez avec Notepad (ou Notepad++, comme vous voulez) le fichier "FXADDINror.map". Ce fichier contient, entre autres, la liste des fonctions de votre programme ainsi que leurs adresses. (Vous verrez qu'il y a aussi pleins de fonctions qui ne vous concernent pas car elles viennent de la fxLib). Voici la partie qui nous intéresse :
_test
0030020c 8 func ,g *
Nous savons donc que l'adresse de cette fonction dans la mémoire de la calculatrice est 0x0030020c et qu'elle a une longueur de 0x8 octets. Or lorsqu'un fichier est chargé dans la mémoire de la calculatrice, il le charge à l'adresse 0x00300000. Ce qui veut dire que l'adresse de notre fonction dans le fichier est en fait 0x30020c-0x300000 = 0x20c.
Donc maintenant le but est de trouver l'octet ayant pour offset (position) 0x20c. Donc sur la colonne tout à gauche de votre éditeur vous verrez une liste de nombre allant de 0x10 en 0x10, vous devez donc trouvez la ligne 0x200. Puis une fois que c'est fait vous recherchez dans cette ligne, la colonne qui a pour en-tête 0xc et vous aurez normalement trouvé l'offset 0x20c. Vous sélectionnez 8 octets à partir de celui-là et vous aurez le contenu de notre fonction.
Et vous obtenez donc cette liste d'octets
E4 09 74 01 00 0B 60 43Ouais, mais je vous ai déjà dit qu'une instruction prenais 2 octets, donc on peut les regrouper par instructions:
E409
7401
000B
6043
Remarquez qu'il y a 4 instructions.
Désassembler et trouver la signification de chaque instruction
Je vais vous présenter un document PDF qui contient tout ce qu'il y a à savoir sur notre processeur. Nous prendrons celui des SH3 (car les SH4 pour casio ont les mêmes instructions que les SH3 alors qu'à la base ils en ont plus et si vous prenez celui des SH4, vous aurez donc des instructions en trop si vous prenez celui des SH4).
Renesas SH-3/SH-3E/SH3-DSP Software Manual ou "The fucking manual" (notez que tout ce qui concerne SH-3E et SH3-DSP ne nous concerne pas).
Si vous l'ouvrez, vous risquez d'être un peu perdu tellement le machin est gigantesque (581 pages). Je vous rassure on va ne pas tout lire
Normalement vous devriez avoir une table des matières qui devrait s'afficher quelque part dans votre logiciel. Si vous la voyez pas, essayer de trouver un réglage qui l'affiche ou essayez d'utiliser Foxit Reader, c'est ce que j'utilise car il est plus rapide que celui d'adobe et il affiche par défaut cette table.
Dans la doc, les instructions sont représentées en binaire, donc on va traduire nos instructions pour que ce soit plus simple de les reconnaitre. (Utilisez la calculatrice windows en mode programmeur si vous ne savez pas le faire ou que vous avez la flème)
1110 0100 0000 1001
0111 0100 0000 0001
0000 0000 0000 1011
0110 0000 0100 0011
Allez dans "Section 7 Instruction Set" > "7.2 Instruction Set in Alphabetical Order" (milieu de la page 113).
Vous allez trouver un gros tableau avec à chaque ligne un code en binaire (avec les parties variables remplacés par des lettres).
Comparez les 4 premiers bits (on appelle ça un nibble) c'est à dire dans notre cas 1110 de chaque lignes. Puis si vous en trouvez un qui correspond, vous vérifiez si la suite correspond.
Les parties remplacées par des lettres peuvent avoir n'importe quel contenu.
Notez qu'il peut y avoir aucune instruction correspondante dans certains cas.
Aide
Banane !
Vers la fin de la page 118
Réponse pour la première ligne : 1110 0100 0000 1001
Patate !
MOV #imm,Rn [courier]1110nnnniiiiiiii[/courier]
Donc on a trouvé l'instruction en question, maintenant on va reconstituer les paramètres :
nnnn correspond à 0100=0x4 donc n=4
iiiiiiii correspond à 00001001=0x9 donc imm=9Et donc notre instruction complète est
MOV #9,R4Même sans trop de connaissances, et sachant que R4 est un registre, on peut deviner que cet instruction met la valeur 9 dans le registre r4.
Notez que vous pouvez rencontrer cette instruction sous cette forme :
MOV #h'9,R4
Car h'n veux dire que n est en notation hexadécimal (équivalent de 0x) sauf qu'ici 9=0x9=h'9 donc ça change rien.
Vous vous posez peut-être la question à quoi correspondent ces nnnn, dddd, iiii ou mmmm :
-
nnnn, mmmm sont des numéros de registres généraux (r0 à r15). Leurs valeurs doivent remplacer respectivement "n" et "m" dans l'instruction donnée dans la doc.
-
iiii (ou
iiiiiiii, iiiiiiiiiiiii) contient une valeur immédiate, c'est à dire une valeur contenu directement dans l'instruction. Cette valeur remplacera "imm" dans l'instruction donnée dans la doc.
-
dddd (ou
dddddddd, ddddddddddddd) contient une "déplacement" (displacement), c'est à dire une valeur qu'on va ajouter à une adresse (généralement contenu dans un registre) afin d'atteindre un point précis. Cette valeur remplacera "disp" dans l'instruction donnée dans la doc.
Réponse pour la deuxieme ligne : 0111 0100 0000 0001
Patate^2
ADD #imm,Rn [courier]0111nnnniiiiiiii[/courier]
Reconstituons les paramètres :
[courier]nnnn [/courier]correspond à 0100=0x4 donc n=4
[courier]iiiiiiii [/courier]correspond à 0000001=0x1 donc imm=1
et donc notre instruction complète est
ADD #1,R4
Encore une fois ça me semble pas trop difficile à comprendre : On ajoute "1" au registre r4
Réponse pour la troisième ligne : 0000 0000 0000 1011
Patate^3
RTS
0000000000001011
Cette fois-ci il n'y a pas de paramètres. Et si je vous demande à quoi sert cette instruction, je ne pense pas que vous trouverez. A moins qu'on utilise la documention pour qu'elle nous donne une description.
Lire la description
Pour mieux comprendre ce que fait l'instruction RTS, vous allez aller dans ces chapitres :
"Section 8 Instruction Descriptions" > "8.2 Instruction Description (Listing and Description of Instructions
Common to the SH-3, SH-3E and SH3-DSP)"
puis trouvez votre instruction.
TFM a écrit :
Returns from a subroutine procedure.
On apprend donc que cette instruction sert à revenir d'une "subroutine"(sous-routine en français.. oui, je sais ça aide pas). En C on appellerais cette sous-routine "une fonction" tout simplement. Donc en gros, il appelle ça à la fin d'une fonction pour revenir là où il était avant de commencer la fonction.
La bible en version numérique a écrit :
The PC values are restored from the PR, and
the program continues from the address specified by the restored PC value. This instruction is used
to return to the program from a subroutine program called by a BSR or JSR instruction.
Là on entre dans les détails techniques de cette instruction. Donc le registre PR est copié dans le registre PC (je rappelle que PC contient l'adresse d'exécution actuelle. Donc si on modifie le registre PC, le programme ira continuer à l’endroit indiqué). En fait le registre PR est utilisé pour stocker l'adresse d'où est partie la fonction, vous n'êtes pas censé l'utiliser pour autre chose (même si c'est techniquement possible).
Cornichon a écrit :
Note: Since this is a delayed branch instruction, the instruction after this RTS is executed before branching.
Branching = jumper dans une subroutine
On apprend donc que c'est une "delayed branche instruction" et que pour cette raison, l'instruction placé juste après sera exécuté avant de jumper. Vous devez donc faire attention à ce qu'il y a juste après. Vous trouvez peut-être cela bizarre, c'est en fait du à la façon qu'a le processeur de gérer les instructions, ce n'est pas vraiment un choix qui est utile pour nous. Mais en même temps un programme n'est pas fait pour être compréhensible par vous, mais par la machine.
Sous cette description vous trouverez an algorithme en C qui représente ce que fait cette instruction. Pratique pour ceux qui comprennent pas l'anglais de la doc (qui peut, je vous le concède, être assez difficile quand on ne connait pas beaucoup de choses sur le processeur ou en assembleur)
PC=PR+4;
Je vous ai un peu mentit tout à l'heure, PC ne contient pas l'addresse actuelle, mais
l'addresse actuelle + 4. Donc ici on copie bien PR dans PC.
temp=PC;
Delay_Slot(temp+2);
Si vous regardez plus haut dans la doc vous verrez que cette fonction Delay_Slot permet d'executer une autre instruction avant de "sauter" vers un autre endroit. Ici on execute donc l'instruction PC+2 qui correspond bien à la ligne du dessous (une instruction fait deux octets donc PC+2 est l'instruction du dessous)
Réponse pour la quatrième ligne : 0110 0000 0100 0011
Patate^4
MOV Rm,Rn [courier]0110nnnnmmmm0011[/courier]
Instruction complète :
Mov r4,r0
Cette instruction est facile à comprendre sachant qu'on a déjà vu son amie.
Recapitulatif
Bon et bien on a réussi à désassembler chaque instruction, donc si on récapitule tout ça nous donne
MOV #9,R4 ; On met 9 dans le registre r4
ADD #1,R4 ; On ajoute 1 à r4
RTS ; On retourne de la fonction
Mov r4,r0 ; Mais avant on copie r4 dans r0
(En assembleur, les commentaires sont après le ";" et il n'est pas nécéssaire de mettre des ; à la fin des lignes sauf dans le cas ou vous commentez bien evidement)
Si on compare à notre code en C, ça ressemble assez, on fait une variable qui vaut 9 et on lui ajoute 1.
En voyant ça, je sais pas vous, mais mois je me pose plusieurs questions :
-
Pourqoi à la fin pourquoi il copie r4 dans r0 ?
Par convention, on fait en sorte que r0 contienne la valeur de retour de votre fonction. Ce qui permet de la récuperer après assez facilement.
-
Pourquoi au lieu de faire
MOV #9,R4
ADD #1,R4
Il ne fait pas directement
Mov #10,r4
Là, la réponse est simple, le compilateur n'a juste pas optimisé notre code C. Donc en fait ici c'est plutôt de notre faute. Le compilateur fait certaines optimisations, mais il n'est pas meilleur que l'homme dans ce domaine (en principe).
-
Pourquoi au lieu de faire
MOV #9,R4
ADD #1,R4
RTS
Mov r4,r0
Il ne fait pas directement
MOV #9,R0
RTS
ADD #1,R0
Cette fois-ci ce n'est pas de notre faute, le compilateur n'a pas optimisé, et en C on ne peut rien y faire. Cependant une fois que vous connaissez l'assembleur vous pouvez optimiser vous-même certaines fonctions qui sont très utilisé dans votre code et qui ont besoin d'être rapide.
Donc la fonction la plus optimisée aurait été
RTS
MOV #10,R0
Et on a divisé la taille (et en principe la durée) de cette fonction par 2.
Quand on est feignant
Ou quand un fichier binaire est très long, on ne va pas s'amuser à tout désassembler à la main. On utilise donc le désassembleur. Je vais vous en présenter deux :
-
SuperH3 disassembler : C'est un simple désassembleur. Il suffit d'y glisser votre fichier binaire et il vous ressortira un fichier texte avec la liste des instructions. L'avantage est que c'est très rapide à utiliser et qu'il traite sans problème n'importe quel fichier binaire. L'inconvénient majeur est que contrairement au désassembleur suivant, on ne peut pas exécuter le code étape par étape et voir ce qui ce passe.
Si vous voulez le tester, vous téléchargez le fichier. Vous décompressez dans un dossier puis glissez votre g1a sur l'exécutable. Si tout se passe bien vous verrez la console s'afficher en éclaire et un fichier output.txt apparaitra dans le même dossier. Vous l'ouvrez et allez à l'offset 0x0020c (les offsets sont la colonne de gauche). Vous verrez normalement le code qu'on a désassemblé précédemment.
-
Casio fx-9860G SDK : En principe vous le connaissez bien celui-là, sauf que vous ne l'avez sans doute jamais utilisé comme désassembleur. Mais avant de l'utiliser, on va modifier un tout petit peu notre code. Vous allez mettre appeler test(); en haut de la fonction AddIn_main, juste après les déclarations de variable afin que l'émulateur exécute notre fonction test().
Ensuite vous allez aller dans le menu "view" puis ouvrez les sous-fenêtres "Disassembled code", "Registers". Bon la fenêtre "Disassembled code" ne contient pas grand-chose pour l'instant, on peut par contre déjà parler de la fenêtre "Registers". Vous vous en doutez, elle contient l'état actuel des registres; vous retrouvez donc nos r1 à r15, d'autres registres qui ont un rôle définie (je vous laisse lire la doc pour savoir à quoi ils servent), et dans la dernière ligne vous pouvez trouver les flags, dont notre fameux flag T. Pour info, à chaque fois qu'un registre est modifié dans cette fenêtre, la ligne du registre sera écrite en rouge lorsque la valeur est réellement modifiée, et elle sera en vert lorsqu'elle est la même qu'avant (dans le cas où vous écrivez 0 et qu'il y avait 0 avant par exemple).
Maintenant qu'on a préparé l'interface, vous allez lancer l'émulateur comme d'habitude, lancez aussi votre programme. Vous verrez normalement le fameux "This application is sample add-in" sur l'écran, et vous allez cliquer sur stop (le carré bleu à côté de celui qui lance l'émulateur). Vous allez ensuite aller sur la fenêtre "Disassembled code". Faites clic droit sur une des lignes (peu importe laquelle) puis cliquez sur "goto...". Ce menu nous permet de nous déplacer rapidement à un autre endroit de la mémoire. Nous, nous voulons voir notre fonction test, on va donc écrire dans la fenêtre qui apparait 30020c. Vous vous retrouvez normalement bien sur la bonne ligne et vous devriez retrouver le code que nous avons désassemblé.
C'est bien beau d'avoir le code désassemblé, mais si on l'utilise c'est pour voir la situation évoluer pas à pas, il faut donc qu'on se débrouille pour que l'émulateur s'arrête pile poil au début de la fonction. On va donc utiliser un breakpoint à l'adresse de début de notre fonction. Un braekpoint est une adresse où on ordonne à l'émulateur de s'arrêter lorsqu'il y accède tout simplement. Pour cela vous faites clic droit sur la ligne 30020c et "Set breakpoint".
Maintenant pour il faut relancer l'émulateur car au point où nous en somme votre fonction a déjà été exécuté. Pour relancer l'émulateur, à ce que je sache, si on ne veut pas recompiler notre AddIn, il faut cliquer sur "reload project" (le bouton à gauche de la compilation). Vous relancez ensuite l'émulateur et votre programme, et si tout se passe bien l'émulateur devrait s'arrêter et vous montrer la fenêtre du code désassemblé, avec notre ligne 30020c comportant le rond rouge signifiant qu'il y a un break point et une flèche jaune signifiant que l'émulateur est actuellement sur cette ligne.
Ensuite normalement si vous cliquez sur "trace into" il est censé passer à l'instruction suivante. Vous testerez vous mêmes, mais personnellement lorsque le code est de base en C, l'émulateur a tendance à ne pas vouloir faire du "pas à pas" et à sauter pas mal de lignes, mais parfois ça fonctionne. Le seul moyen que j'ai trouvé c'est de mettre des breakpoints à chaque lignes. C'est chiant, mais on ne peut pas vraiment faire autrement.
Donc si il saute trop de ligne (notamment s'il ne vous montre pas la ligne "mov r4,r0", c'est ce qui se passe chez moi). Cliquez sur le bouton "reload project" relancez l'émulateur et lorsqu'il a atteint notre fonction placez des breakpoint à chaque ligne.
En principe lorsqu'il aura passé la première ligne vous verrez dans la liste des registre, le r4 qui sera en rouge et vaudra 9, si vous faites la ligne suivant il vaudra 10 (enfin 0xA), bref comme on avait prévu. (Vous verrez aussi la ligne PC qui sera en rouge car l'adresse actuelle a changé)
Notez que la ligne actuellement surligné dans le code désassemblé est celle qui sera exécuté juste après. Vous pouvez voir aussi que l'émulateur n'a pas l'aire de respecter le fait que le registre PC est la ligne actuelle + 4. Lui il fait juste la ligne actuelle, me demandez pas pourquoi, peut-être pour nous embrouiller, comme l'histoire du "trace into" qui bug ?
La stack
Vous vous êtes peut-être poser la question de ce qu'il se passe lorsqu'on a besoin de plus de registres, ou alors lorsqu'on appelle une fonction dans une fonction (le registre pr contient déjà l'adresse de la suite de la première fonction et donc si on va dans une autre fonction ce registre sera effacé et ça changera tout)
Dans la mémoire, une partie est réservé à contenir la stack. Stack veut dire pile, donc comme son nom l'indique, on va empiler les données dans cette mémoire. Et généralement on va y envoyer des infos, dans un ordre puis les retirer dans le sens inverse. On peut comparer ça à une pile de livre. Lorsque vous en poser un vous recouvrez celui de dessous, mais si vous voulez récupérer celui du dessous, il sera plus simple de retirer le premier livre.
Une utilisation assez courante de la stack est au début et à la fin d'une fonction. Si votre fonction a besoin du registre r10, r11 et r12 par exemple. Ces registres doivent avoir la même valeur au début et à la fin de la fonction. Pour cela on utiliser cette structure :
r10 -> stack
r11 -> stack
r12 -> stack
//Votre code
stack -> r12
stack -> r11
stack -> r10
Ainsi dans votre code vous pouvez utiliser les registres r10,r11 et r12 sans problème. Vous pouvez faire de même pour le regstre PR afin de pouvoir utiliser une fonction dans une fonction.
Donc cette pile se situe à un certain endroit en mémoire. Et pour savoir où nous en sommes dans la pile nous devons avoir accès a une valeur qui sera toujours disponible et qui contiendra l'adresse du haut de cette pile. C'est à dire l'adresse du dernier endroit réservé. Ici la pile marche un peu à l'envers car on va commencer par l'adresse la plus élevé de la pile, puis lorsqu'on reserve une case de la pile, on va soustraire le nombre d'octet que l'on réserve à l'adresse. Je ne suis pas sûr que ce soit très clair pour vous, donc je vais juste vous préciser que cette fameuse adresse est stockée en r15 avant de passer à un exemple.
Donc on traduirait le code précédent comme ça en assembleur
add #-4,r15 ; On reserve 4 octet car un regsitre fait 4 octet
mov.l r10,@r15 ; On envoi r10 dans l'addresse qu'on lui a reservé
; (le @ indique donc qu'on déplace r10 vers l'addresse pointé par r15 et non vers r15)
add #-4,r15 ;On recommance pour les autres
mov.l r11,@r15
add #-4,r15
mov.l r12,@r15
; votre code
mov.l @r15,r12
add #4,r15
mov.l @r15,r11
add #4,r15
mov.l @r15,r10
add #4,r15
Vous pouvez vous dire que c'est un peu répétif, et c'est pour ça qu'il existe une instruction qui permettre d'enlever les "add"
mov.l r10,@-r15 ; Ici il soustrait 4 avant de copier r10 vers @r15
mov.l r11,@-r15
mov.l r12,@-r15
; votre code
mov.l @r15+,r12 ; ici il ajoute 4 après de copier
mov.l @r15+,r11
mov.l @r15+,r10
Les conventions
En assembleur, si on en a envie, on peut faire n'importe quoi, rien n'est vraiment bloqué. Par exemple si vous voulez utiliser le registre PR ou r15 pour stocker des données, c'est possible le compilateur ne vous dira rien, mais vous faites une grosse erreur. Des conventions ont été mis en place afin que les programmes/fonctions s'entendent entre eux.
Comme nous l'avons vu, Casio a mis en place la convention que le registre r15 contient l'adresse actuelle de la stack. Renesas (les fabricants du processeur) ont mis en places d’autres registres réservés : tout ceux dont le nom n'est pas r0 à r15.
Donc pour l'instant, d'après ce que nous savons, nous pouvons modifier les registres r0 à r14 sans problème. En fait non. Les registres r0 à r7 peuvent être modifié sans modération ni sauvegarde. Les registres r8 à r14 doivent être sauvés comme nous l'avons vu dans le chapitre précédent avant de les modifier.
source
Il existe d'autres conventions, notamment celles nous permettant d'appeler une subroutine. Avant d'appeler une subroutine, vous devez stocker les paramètres de cette subroutine dans r4, r5, r6 et r7.C'est à dire que lorsque vous faites
fonction(1,2,3,4,5)le 1 est stocké dans r4, le 2 dans r5, 3 dans r6 et 4 dans r7. Quant au 5 (ainsi que tout autre paramètres supplémentaires), il sera stocké dans la stack (je n'ai pas vraiment toucher à des fonctions de plus de 4 paramètres donc si vous voulez en savoir plus sur les paramètres en stack, faites un code en C et observez le pour mieux comprendre). A la fin d'une subroutine, la valeur de retour est stockée dans le registre r0.
L'alignement mémoire
Pour les opérations sur 4 octets comme MOV.L, il existe une subtilité qu'il faut bien noter : elle ne peut modifier ou accéder qu’à une adresse étant un multiple de 4. Il en va de même pour MOV.W qui ne peut accéder/modifier que des adresse étant multiple de 2.
En principe, il suffit de le savoir et de bien faire attention pour ne pas faire d'erreur. Il y a cependant un cas où vous pourrez avoir quelques problèmes. Lorsque vous stockez une variable en stack. Si vous voulez stocker un byte ou un Word, vous allez sans doute vouloir utiliser
mov.b r0,@-r15 ; soustrait 1 à r15 est stocke r0 dans @r15
ou
mov.w r0,@-r15; soustrait 2 à r15 est stocke r0 dans @r15
Mais si plus loin, que ce soit dans votre code, ou dans une autre fonction, cette instruction est utilisée
mov.L r0,@-r15
r15 ne sera plus un multiple de 4, et votre addin va planter.
Vous me direz sans doute que dans ce cas, il suffit que r15 soit un multiple de 4 avant d'appeler une fonction ou avant de retourner de la vôtre ? Et bien en fait non c'est pire. Vous savez peut-être qu'il existe des interruptions : c'est à dire des morceaux de codes qui seront exécutés à différents moments en plein milieu du votre sans vous demander votre avis. Et bien si une interruption s'exécute en plein milieu de votre fonction et que r15 n'est pas un multiple de 4, votre AddIn plantera. (Merci à SimonLothar pour son explication, j'ai galéré la dessus) Donc dans tous les cas, peu importe le moment
r15 doit toujours être un multiple de 4
Vous allez sans doute penser qu'on est donc obligé de stocker que des longword en stack, ce qui n'est pas le cas, on peut très bien stocker d'autres types, il faut juste réfléchir un peu plus.
Vous ne pouvez donc pas faire ça
mov.w r4,@-r15 ; ici, r15 n'est plus un multiple de 4
mov.b r5,@-r15 ; r15 est maintenant un nombre impaire
mov.b r6,@-r15 ; ici, c'est bon r15 est un multiple de 4
Mais vous pouvez faire ça :
shll2 r4 ; on décale r4 de deux octets sur la gauche (equivalent en C de r4=r4<<2)
shll r5,r5 ; decale r5 d'un octet sur la gauche
add r5,r4
add r6,r4 ; On ajoute les trois valeurs (qui seront donc tous placés dans leurs octets respectifs automatiquemnt)
mov.b r4,@-r15 ; et on sauvegarde
On aurait aussi pus faire
add #-4,r15 ; on reserve nos 4 octets
mov r4,r0
mov.w r0,@(2,r15) ; on met r0 dans r15+2 (on ne peut pas utiliser directement r4 ici : cf la bible)
mov r5,r0
mov.w r0,@(1,r15)
mov.w r6,@r15
et voilà, et dans ces deux derniers cas r15 a toujours été un multiple de 4. On peut sans doute trouver d'autres solutions plus ou moins optimisées en fonction de situations, mais je vous laisse vous creuser la tête pour ça.
Annexe vocabulaire
Sign-extended
Description des fonction MOV à données imédiate page 216 a écrit :
Description: Stores immediate data, which has been sign-extended to a longword, into general
En informatique, un octet négatif est représenté par un nombre superieur ou égale à 0x80. (si c'était deux octet ça serait 0x8000). Par exemple
0 = 0x00
1 = 0x01
126 = 0x7e
127 = 0x7f
128 : impossible
-- negatifs --
-1 = 0xff
-2 = 0xfe
-126 = 0x82
-127 = 0x81
-128 = 0x80
-129 : impossible
Or si le conteneur est un longword (4octets) :
0 = 0x00000000
1 = 0x00000001
126 = 0x0000007e
127 = 0x0000007f
128 : 0x00000080
-- negatifs --
-1 = 0xffffffff
-2 = 0xfffffffe
-126 = 0xffffff82
-127 = 0xffffff81
-128 = 0xffffff80
-129 = 0xffffff7f
Maintenant immaginez qu'on copie la valeur -128 d'un byte vers un longword sans faire attention au signe on obtient
en byte -128=0x80
en longword 0x00000080=128
Vous venez de changer la valeur réelle car vous n'avez pas fait attention au signe.
"sign-extended to a longword" : Veut donc dire qu'il fait attention à ce que si la valeur est supérieure ou égale à 0x80, il complète avec des 0xFF sinon avec des 0x00.
General
Description des fonction MOV à données imédiate page 216 a écrit :
Description: Stores immediate data, which has been sign-extended to a longword, into general
"general" est ici l'abréviation de "General register", ou Registre géréral. Il en existe 16 : r0 à r15. (les autres ne sont pas des registres généraux)
Fichier joint
Citer : Posté le 24/03/2018 12:31 | #
o_O HA WAW ! ça fonctionne ! merci ! x)
Ajouté le 24/03/2018 à 21:31 :
Le code est assez long, j'essaie de faire un pong.
La j'essaie juste de faire une balle qui bouge tout seul, bref pas grand chose...Mais j'ai des problèmes de compilation
.export _ini
_ini:
add #-12, R15
mov #0,R0 ;BallX = 0
mov.b R0, @(8,R15) ;BallX address
mov #0,R0 ;BallY = 0
mov.b R0, @(4,R15) ;BallY address
mov #0,R0 ;setUp = 0x02 /*0x01 = up/down && 0x02 = left/right*/
mov.b R0,@R15 ;setUP address
_main:
mov.b @(4,R15),R0 ;met l' address BallY dans R0
mov R0, R2 ;met l' address BallY dans R2
mov.b @(8,R15),R0 ;met l' address BallX dans R0
mov R0,R1 ;met l' address BallX dans R1
mov.b @R15,R0 ;met l' address setUp dans R0
;ini BallX
extu.b R1,R1 ;permet d'utiliser que le premier octet de R1
mov #128, R3
TST R3,R1 ;test si BallX < 128
bt L0 ;si (BallX > 128) saute 4ligne
mov #127,R1 ;...don't put #0x80 in R1...
xor #2,R0 ;...setUP^0x02 (up or down)
L0:
CMP/PL R1 ;if(BallX > 0)0->T
bt L1 ;if(BallX < 1)...
mov #0, R1 ;...don't put #0x80 in R1...
xor #2, R0 ;...setUP^0x02 (up or down)
L1:
;manage BallX ;else
extu.b R0,R0 ;permet d'utiliser que le premier octet de R0
tst #2,R0 ;test si la balle doix aller a gauche ou a droite
bt L2 ;if(setUp&0x01)
mov #1,R2 ;r2 = 2
sub R1,R2 ;R1 = R1-r2
sett ;1->T
bt L3 ;si la balle va a droite
L2:
add #1,R1 ;on lui ajoute 1
L3:
;ini BallY
extu.b R2,R2 ;permet d'utiliser que le premier octet de r2
mov #64,R3
tst R3,R2 ;test si BallY < 64
bt L4 ;si (BallX > 64) saute 4ligne
mov #63,R2 ;...don't put #0x80 in R1...
xor #1, R0 ;...setUP^0x02 (up or down)
L4:
CMP/PL R2 ;if(BallX > 0)0->T
bt L5 ;if(BallX < 1)...
mov #0,R2 ;...don't put #0x80 in R1...
xor #1,R0 ;...setUP^0x01 (up or down)
L5:
;manage BallY ;else
extu.b R0,R0 ;permet d'utiliser que le premier octet de R0
mov #1,R3
tst R3, R0 ;test si la balle doix aller a haut ou en bas
bt L6 ;si elle vas en haut
mov #1,R2 ;r2 = 2
sub R1,R2 ;R1 = R1-r2
sett ;1->T
bt L7 ;si la balle va en bas
L6:
add #1,R2 ;on lui ajoute 1
L7:
;renvoie
mov.b R0,@R15
mov R1,R0
mov.b R0,@(8, R15) ;revoit le BallX
mov R2,R0
mov.b R0,@(4, R15) ;revoit le BallY
;screen management
mov.l VRAMCLS,R2
jmp @R2
nop
mov.b @(4, R15), R0 ;met l' address BallY dans R4
mov R0,R4
mov.b @(8, R15), R0 ;met l' address BallX dans R5
mov R0,R5
mov #2,R6
mov #1,R7
mov.l ML_fl_cr,R2
jmp @R2
nop
mov.l ML_dsp_vr,R2
jmp @R2
nop
mov #2, R4
mov.l setFPF,R2
jmp @R2
nop
bra _main
VRAMCLS:
.DATA.L _ML_clear_vram
ML_fl_cr:
.DATA.L _ML_filled_circle
ML_dsp_vr:
.DATA.L _ML_display_vram
setFPF:
.DATA.L _setFPS
.end
Les erreurs que j'ai:
C:\Users\BG\Documents\CASIO\fx-9860G SDK\ASM_v1\test.src(107) : 200 (E) UNDEFINED SYMBOL REFERENCE
C:\Users\BG\Documents\CASIO\fx-9860G SDK\ASM_v1\test.src(109) : 200 (E) UNDEFINED SYMBOL REFERENCE
C:\Users\BG\Documents\CASIO\fx-9860G SDK\ASM_v1\test.src(111) : 200 (E) UNDEFINED SYMBOL REFERENCE
C:\Users\BG\Documents\CASIO\fx-9860G SDK\ASM_v1\test.src(113) : 200 (E) UNDEFINED SYMBOL REFERENCE
*****TOTAL ERRORS 4
*****TOTAL WARNINGS 0
(PS: j'ai pas fini de mettre les commentaires donc il y surement des erreurs)
Citer : Posté le 24/03/2018 21:35 | #
Ben faut ajouter ML à ton projet, ainsi que la lib qui définit setFPS(), à savoir useful de Dodormeur si je me souviens bien.
Il y a aussi plusieurs problèmes dans ton code. Je cite, en vrac, et de façon non exhaustive :
- Pas de rts à la fin de _ini ?
- Dans le début de _main, tu oublies d'initialiser r0 à 0 ou d'utiliser extu.b
- mov #128, r3, tu vas probablement avoir une surprise
- On n'appelle pas VRAMCLS avec jmp, sinon on ne revient jamais. Il faut utiliser jsr.
- Et du coup il faut sauvegarder et restaurer pr.
- Que vient faire bra _main ici ?
- Le delay slot du bra _main en question c'est des octets de données, t'as remarqué ?
Citer : Posté le 27/03/2018 20:49 | #
Non parce que a la base je voulais juste faire une boucle simple qui fait bouger un sprite donc dans mon fichier C je voulais juste appeler une fonction qui fait tout.
Corriger (Quand on fait un "mov" r0 n'est pas mis a la valeur appeler ? donc en gros (mov 5, r0) -> (r0 |= 5) ?)
J'ai remplacer par mov #h'80, r3, c'est bon ? Ou alors r3 est un registre spécial ? (d’après Jean-Michel wiki: https://en.wikipedia.org/wiki/Calling_convention r3 est un registre qu'on peut modifier donc je comprend pas )
- Et du coup il faut sauvegarder et restaurer pr.
Je sais pas encore comment on restaure pr mais je vais trouver.
- Le delay slot du bra _main en question c'est des octets de données, t'as remarqué ?
Bra _main est la parce que je voulais boucler _main mais je sais pas comment on fait pour retourné a _main et quand j'ai analyser un while ça mettais bra L245 et L245 c’était le label de la boucle (ou alors j'ai rien baver du code )
D’ailleurs, comment on trouve le delay slot d'une instruction
Ch'ui vraiment une quille, je pige a peine de ce que je fait et j'arrive rien a faire part moi - même.
Je doit quand même avoué que j'ai pas masse de temps pour apprendre avec les examens qui arrive a grand pas.
Citer : Posté le 27/03/2018 21:02 | #
Quand on fait un "mov" r0 n'est pas mis a la valeur appeler ? donc en gros (mov 5, r0) -> (r0 |= 5) ?
Ouh là. mov 5, r0 donne r0 = 5. Mais si tu regardes dans le manuel, tu verras que le mov à opérande immédiate (ie. #...) fait une extension de signe tandis que mov.b @..., r0 non. Autrement dit quand tu utilises le second, seul l'octet de poids faible de r0 est changé, le reste tu dois te démerder pour le remettre à 0.
J'ai remplacer par mov #h'80, r3, c'est bon ?
Nope. Et ce n'est pas lié au registre. Le mov avec une opérande immédiate ne permet d'utiliser que des valeurs signés sur 8 bits, soit entre -128 et 127... si tu mets 0x80 ça va faire -128
Je sais pas encore comment on restaure pr mais je vais trouver.
Jette un oeil autour de sts et lds. Cas super-typique :
sts.l pr, @-r15
...
lds.l @r15+, pr
rts
nop
Non, tu ne peux pas faire le lds.l après le rts, parce que rts « capture » la valeur de pr avant d'exécuter le delay slot.
Bra _main est la parce que je voulais boucler _main mais je sais pas comment on fait pour retourné a _main
...
bra _main
/* un delay slot valide, par exemple : */
nop
T'étais pas loin.
D’ailleurs, comment on trouve le delay slot d'une instruction
Cherche dans la doc, tu trouveras facilement quelles instructions possèdent des delay slots. En gros, toutes celles qui sautent, rien de plus compliqué.
Au cas où, je rappelle le principe d'un delay slot... quand le processeur fait un saut, il perd un cycle parce qu'un mécanisme qui permet d'exécuter plusieurs instructions en même temps, appelé pipeline, ne peut pas suivre. Pour éviter de perdre un cycle, il faudrait que le pipeline connaisse la destination du saut avant que le saut soit fait, ce qui est impossible...
Du coup les ingénieurs de Renesas ont inventé un truc un peu exotique (pas présent sur les architectures classiques, en tous cas) : le delay slot. Quand il voit le saut, il calcule la destination du saut, mais au lieu de sauter tout de suite, il exécute d'abord l'instruction suivante, ce qui permet de remplir l'instruction perdue dans le pipeline.
On appelle donc delay slot d'un saut l'instruction qui se trouve juste après, et qui est exécutée juste avant de sauter. Par exemple, dans le code suivant...
jmp @r4
mov #2, r1
Le processeur charge 1 dans r0, puis voit qu'il y a un saut, calcule la destination du saut, mais avant de sauter, il fait rentrer l'instruction mov #2, r1 dans la « bulle » (le cycle perdu) du pipeline, charge 2 dans r1, puis saute. Cela permet de gagner un cycle.
Alors souvent, le delay slot contient un nop, mais tant pis. Par contre toi tu n'as pas mis d'instruction dans le delay slot, et du coup les deux octets qui suivent ton bra sont les deux premiers octets de l'adresse de ML_clear_vram. Si le processeur tente d'exécuter ça il va certainement gueuler dans le meilleur cas, faire silencieusement une connerie dans le pire...
Citer : Posté le 07/06/2018 18:31 | #
(Merci encore une fois Lephenixnoir pour tes explications <3)
Bon, bon, bon...
Je m'excuse mais n'ayant aucune source (à part syscall.src) pour savoir comment on écrit un .src je me trouve bloquer à la compilation (encore).
(Cette fois-ci j'ai fait un truc où je prends pas trop de risques.)
Voila les erreurs que j'ai:
asm.src(1) : C2500 (E) Illegal token "."
asm.src(4) : C2400 (E) Illegal character "#"
voila mon code.
.export _counterPlus
_counterPlus:
ADD #1, r4
RTS
NOP
.END
Si quelqu'un peut m'expliquer le pourquoi du comment ça ne fonctionne pas et si une personne à des sources à me donner pour que je comprenne la logique derrière les .src (je n'ai pas très la motivation de piffé à l'infini )
Citer : Posté le 07/06/2018 18:47 | #
Ça m'a l'air valide tout ça. Je peux voir le détail du log de compilation et le contenu de ton fichier g1w ?
Citer : Posté le 07/06/2018 20:01 | #
Bien sur
log
Executing Hitachi SH C/C++ Compiler/Assembler phase
set SHC_INC=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\include
set PATH=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin
set SHC_LIB=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin
set SHC_TMP=C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\Debug
WARNING: The following dependant file(s) do not exist: "myLib.h". Line: 77
"C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin\shc.exe" -subcommand=C:\Users\BG\AppData\Local\Temp\hmkADC1.tmp
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(1) : C2500 (E) Illegal token "."
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(4) : C2400 (E) Illegal character "#"
HMAKE MAKE UTILITY Ver. 1.1
Copyright (C) Hitachi Micro Systems Europe Ltd. 1998
Copyright (C) Hitachi Ltd. 1998
ERROR: Process failed with return code: 1
Build was not successful.
g1w
[DLSimProject]
Name=DEBADD
Version=1
Model=:fx-9860G.dlm
SourcePath=SRC
MemoryPath=INIT
MemCardPath=SDCard
[Program1]
Program=DEBADD.G1A
Debug=Debug\FXADDINror.dbg
LoadAddress=80000000:90100000
[Files]
SourceFile=:DEBADD.c
SourceFile=:myLib.c
SourceFile=:asm.src
HeaderFile=:myLib.h
Citer : Posté le 07/06/2018 20:09 | #
En gros je soupçonne que ton fichier se fasse compiler comme un programme C... même si je suis pas totalement sûr. En tout cas ce # est très bien où il est et la directive aussi. Hmm...
Citer : Posté le 07/06/2018 20:51 | #
Ha yes du coup je suis bloqué ?
Peut-être que ça viens du code de base:
(Ou du fait que je l'ai appelé "asm.src" (?))
#include "fxlib.h"
#include "asm.src"
extern counterPlus(unsigned char);
int AddIn_main(int isAppli, unsigned short OptionNum)
{
unsigned char counter = 0;
while(1)
{
counterPlus(counter);
ML_clear_vram();
PrintHex(0, 0, counter);
ML_display_vram();
setFPS(2);
}
}
#pragma section _BR_Size
unsigned long BR_Size;
#pragma section
#pragma section _TOP
int InitializeSystem(int isAppli, unsigned short OptionNum)
{
return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}
#pragma section
Citer : Posté le 07/06/2018 21:24 | #
Non, ça vient pas de là. Et justement « asm.src » c'est un nom valide. Tu me mets une archive avec le projet complet pour que je puisse tester ?
Citer : Posté le 08/06/2018 10:12 | # | Fichier joint
Voila
(merci beaucoup )
Citer : Posté le 08/06/2018 10:34 | #
Bon, j'ai fini par trouver.
Je ne te laisserai pas partir avant que tu m'aies expliqué pourquoi c'est absurde.
Citer : Posté le 08/06/2018 10:45 | #
on include seulement des fichiers d'entête (?) et la vu que c'est .src bah ça le gère comme un fichier .h
Citer : Posté le 08/06/2018 10:48 | #
Ce n'est pas tout. Un #include ça copie littéralement les contenus du fichier inclus dans celui qui se fait compiler. Ton code assembleur se faisait donc coller en haut d'un fichier C et compiler avec, une vraie boucherie.
Ne mélangez jamais deux langages !
Citer : Posté le 08/06/2018 10:52 | #
Ne mélangez jamais deux langages !
OK, ok, ok, ok
Du coup, j'ai d'autres erreurs:
Executing Hitachi SH C/C++ Compiler/Assembler phase
set SHC_INC=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\include
set PATH=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin
set SHC_LIB=C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin
set SHC_TMP=C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\Debug
WARNING: The following dependant file(s) do not exist: "myLib.h". Line: 77
"C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin\shc.exe" -subcommand=C:\Users\BG\AppData\Local\Temp\hmkAD30.tmp
WARNING: The following dependant file(s) do not exist: "myLib.h". Line: 94
"C:\Program Files\CASIO\fx-9860G SDK\OS\SH\bin\asmsh.exe" -subcommand=C:\Users\BG\AppData\Local\Temp\hmkADEC.tmp
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(1) : 202 (E) ILLEGAL SYMBOL OR SECTION NAME
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(1) : 300 (E) ILLEGAL MNEMONIC
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(8) : 202 (E) ILLEGAL SYMBOL OR SECTION NAME
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(8) : 880 (W) .END NOT FOUND
*****TOTAL ERRORS 3
*****TOTAL WARNINGS 1
HMAKE MAKE UTILITY Ver. 1.1
Copyright (C) Hitachi Micro Systems Europe Ltd. 1998
Copyright (C) Hitachi Ltd. 1998
ERROR: Process failed with return code: 2
Build was not successful.
Citer : Posté le 08/06/2018 11:16 | #
Ne mets pas les headers dans le projet. Le SDK n'aime pas ça, on ne sait pas pourquoi.
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(1) : 300 (E) ILLEGAL MNEMONIC
L'assembleur du SDK ne t'autorise pas à mettre une directive au tout début d'une ligne. Ajoute un espace ou une tabulation avant.
C:\Users\BG\Documents\CASIO\fx-9860G SDK\DEBUG_ADDIN\asm.src(8) : 880 (W) .END NOT FOUND
Et je soupçonne que tu as retiré la tabulation devant ton .END, qui était pourtant très bien à sa place.
Soit dit en passant ne gueulez pas quand vous codez en assembleur. Et ne revenez pas dans les années 80. Utilisez des minuscules.
_counterPlus:
add #1, r4
rts
mov r4, r0
.end
Citer : Posté le 08/06/2018 11:38 | #
Pour les headers, j'ai trouvé une explication, mais elle me semble bancale.
Lorsque l'on renseigne les headers au SDK, il note le chemin relatif vers ces derniers. Or, comme on le sait tous il ne trouve pas les headers. Cependant, j'ai remarqué qu'en copiant les headers dans le dossier où sont créé les .o et .lst, il arrive à les trouver, j'en ai donc déduis qu'il utilise le chemin relatif en partant de ce dossier au lieu du dossier principal du projet (d'ailleurs il me semble avoir réussi a faire marcher les headers avec des chemins absolue). Mais cette théorie me semble tout de même peu probable, car comment laisser passer une erreur aussi bête? Et si c’était vraiment cela, vous ne diriez pas que personne n'a jamais compris, quelqu'un y aurait forcement déjà pensé, non?
Citer : Posté le 08/06/2018 11:49 | #
Ce que tu dis n'est pas insensé puisque compiler depuis le dossier de build est une pratique courante. De plus, personne n'a jamais essayé de comprendre, pour être honnête la plupart des gens s'en contrefoutent.
Non, au fond je pense que tu as raison, leur système de dépendances ne doit pas être terrible.
Citer : Posté le 09/06/2018 18:03 | # | Fichier joint
Bon j'ai repris mon flappy bird à 0, tout allait très bien jusqu'au moment où j'ai écrit cette fonction:
_wall_move:
;timer_wall++
mov.b @r5, r1 ;met "timer_wall" dans r1
add #1, r1 ;r1++
mov.b r1, @r5 ;met r1 dans "timer_wall"
;gestion d'aparition des murs
mov #0, r6 ;buffer addresse test[0]
add r4, r6 ;met l'addresse de test[1] dans r6
mov #0, r0 ;counter buffer
for_2:
;if(test[1+i] == 1)
mov #1, r2
mov.b @r6, r3
cmp/eq r2, r3 ; on test si r2 == r3
bf else_1
;{
;test[0+i]--
add #-1, r6 ;basculer vers test[0+i]
mov.b @r6, r2 ;r2 = test[0+i]
add #-1, r2 ;r2--
mov.b r2, @r6 ;test[0+i] = r2
;if(test[0+i] == -8)
mov #-8, r3 ;-8 dans r3
cmp/eq r2, r3 ;test si r3 == r2
bf/s cul
add #1, r6 ;dekay slot: bascule vers test[1+i]
mov #0, r2 ;r2 = 0
mov.b r2, @r6 ;test[1+i] = r2
cul:
bsr end_if_1
nop
;}
else_1:
;{
mov #0, r2
cmp/eq r2, r3 ; on test si r2 == r3
bf/s end_if_1
mov #h'20, r2
tst r2, r1 ;if(timer_wall&0x20)->t=1
bf end_if_1
;{
mov #0, r1 ;reset r1
mov.b r1, @r5 ;timer_wall = r1
mov #1, r3
mov.b r3, @r6
add #-1, r6
mov.b @r6, r3
mov #127, r3
mov.b r3, @r6
add #1, r6
;}
;}
end_if_1:
add #1, r0 ;counter++
cmp/eq #4, r0
bf/s for_2
add #3, r6
rts
nop
depuis quand je lance l'Émulateur j'ai "Nonexisting memory by data address 880CF7B8"
J'ai enlevé la fonction et même supprimer le fichier "asm.src" et... toujours le même message.
Le menu ne veut même plus s'afficher.
(j'ai mis toutes les sources dans le fichier joins)
PS: faites pas gaffe au label...
Citer : Posté le 09/06/2018 18:16 | #
Alors, concernant le fait que tu as cassé ton projet, un coup de Rebuild All et suppression du dossier Debug aideront sûrement.
Pour le code en lui-même, je ne t'empêche pas de mettre le code C que tu veux émuler en commentaires, mais c'est un peu de mauvais goût... quand tu auras le niveau pour ne plus en avoir besoin (ça viendra assez vite), n'hésite pas à t'en débarrasser.
add r4, r6 ;met l'addresse de test[1] dans r6
On a du mal à voir ce que tu veux faire parce que ce code est identique à mov r4, r6. De plus on ne sait pas trop ce que tes registres représentent. Là il est essentiel que tes commentaires disent :
- Quelques registres servent à quoi
- Quel est le type de données manipulées dans chaque registre
...
mov #-8, r3
cmp/eq r2, r3
Tu fais des choses très dangereuses. Comme je l'ai déjà mentionné, mov.b ne déplace qu'un octet @r6 vers r2. Or r2 contient 4 octets ; les 3 autres ne sont pas modifiés. Tu t'exposes au risque que les trois octets de r2 contiennent autre chose que des 0, et cela peut fausser ton test.
D'abord, tu es sûr sûr sûr que ton tableau test[] est un tableau de char et pas d'int ?
Ensuite, quand tu fais un mov.b ou un mov.w avec l'intention de comparer la valeur ou de faire des calculs dessus, prend le temps de nettoyer les octets supérieurs. Pour cela, tu utilises ext, qui vient en quatre variantes : exts et extu pour dire si la valeur que tu manipules est signée (s) ou pas (u), chacune avec le suffixe .b ou .w selon si tu viens d'utiliser mov.b ou mov.w.
Pour un compteur non-signé sur 2 octets, par exemple :
extu.w r2, r2
Note que mov #imm fait automatiquement cette extension de façon signée. Autrement dit mov #imm agit toujours comme s'il était suivi de exts.b.
Je reprends.
nop
C'est bra dont tu as besoin ici. bsr c'est pour faire un appel de fonction !
Citer : Posté le 09/06/2018 18:43 | #
Alors, concernant le fait que tu as cassé ton projet, un coup de Rebuild All et suppression du dossier Debug aideront sûrement.
J'ai déjà essayé mais aucun résultats :/ (pas grave je vais refaire un projet ).
Comme je l'ai déjà mentionné, mov.b ne déplace qu'un octet @r6 vers r2. Or r2 contient 4 octets ; les 3 autres ne sont pas modifiés. Tu t'exposes au risque que les trois octets de r2 contiennent autre chose que des 0, et cela peut fausser ton test.
désoler j'ai totalement oublié que r2 à 4 octets !
Je me base sur des bouts e code que j'ai désassembler pour comprendre la logique.
D'abord, tu es sûr sûr sûr que ton tableau test[] est un tableau de char et pas d'int ?
pour sûr:
char* wall[15];
wall_move(wall);
Ensuite, quand tu fais un mov.b ou un mov.w avec l'intention de comparer la valeur ou de faire des calculs dessus, prend le temps de nettoyer les octets supérieurs. Pour cela, tu utilises ext, qui vient en quatre variantes : exts et extu pour dire si la valeur que tu manipules est signée (s) ou pas (u), chacune avec le suffixe .b ou .w selon si tu viens d'utiliser mov.b ou mov.w.
c'est notée
(ou alors je fais mov #0, rX avant d'utiliser mov.b ou mov.w (?)).
C'est bra dont tu as besoin ici. bsr c'est pour faire un appel de fonction !
J'ai vraiment du mal avec la doc x) merci beaucoup