Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.
La shoutbox n'est pas chargée par défaut pour des raisons de performances. Cliquez pour charger.

Forum Casio - Vos tutoriels et astuces


Index du Forum » Vos tutoriels et astuces » [Tutoriel] Initiation à l'assembleur SuperH
Ziqumu Hors ligne Membre d'honneur Points: 3055 Défis: 9 Message

[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 43
Ouais, 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=9
Et donc notre instruction complète est
MOV #9,R4
Mê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


Ziqumu Hors ligne Membre d'honneur Points: 3055 Défis: 9 Message

Citer : Posté le 20/10/2013 11:29 | #


Faudrait vérifier, mais il me semble qu'au niveau du jeu d'instruction les SH4 de Casio sont plus proche des SH3 (du moins pour les Graph35/75/95), notamment parce qu'il n'y a pas de FPU (floating point unit, gestion des nombres a virgule directement par le cpu).
Nemhardy Hors ligne Grand maître des Traits d'Esprit Points: 1242 Défis: 54 Message

Citer : Posté le 21/10/2013 18:13 | #


Tiens, je ne sais pas si c'est le bon endroit pour dire ça, mais j'ai remarqué un truc par rapport à la prizm :

Avec GCC, la fonction test ci dessus est directement assemblée sous la forme la plus optimisée dont tu parlais : on a directement

_test:
    rts    
    mov    #10,r0


C'est juste une remarque vite faite et sans conclusion ...
Pierrotll Hors ligne Ancien administrateur Points: 5488 Défis: 41 Message

Citer : Posté le 24/10/2013 01:54 | #


Plop, petit passage éclair par ici, je découvre tout juste ce tuto. J'ai lu en diagonale, mais j'ai 2 petites remarques rapides :
- Il me semble que le SH7705 est 32 bits (et donc les registres aussi de toute évidence)
- Le SDK écrit le code ASM des fonctions C qu'il compile dans des fichiers .lst présents dans le répertoire Debug du projet.Ainsi on peut voir comment son code C est compilé et (souvent assez mal) optimisé.

Ajouté le 24/10/2013 à 02:04 :
Après avoir parcouru les réponses du topic, j'ajoute que Kristaba avait déjà fait un assembleur en g1a, et on s'était amusé à coder et assembler 2-3 trucs simplistes on-calc
Lancelot Hors ligne Membre Points: 1274 Défis: 160 Message

Citer : Posté le 24/10/2013 11:08 | #


Super , Est-ce que tu posteras ce superbe programme sur Planète-Casio ?
Calculatrices : Casio 35+ SH4 (modifiée 75) et fx-CG 20 PRIZM
Projets que je soutiens
Des exemples parmi tant d'autres
Pokémon Jade de Dododormeur
Zelda de Smashmaster
Super Geek Brothers de Siapran
Mes Programmes
Mes Programmes
Mes Projets
Mes Projets
ColorLib
Add-ins Jetpack Joyride et Pac-Man sur PRIZM (les 2 non commencés mais en réflexion)
A la recherche des sprites jetpack Joride si quelqu'un les a en couleur
Ziqumu Hors ligne Membre d'honneur Points: 3055 Défis: 9 Message

Citer : Posté le 24/10/2013 12:37 | #


Pierrotll a écrit :
- Il me semble que le SH7705 est 32 bits (et donc les registres aussi de toute évidence)
Je confirme (le plus gros type géré par le processeur est le longword qui fait donc 32 bit aussi)
Pierrotll Hors ligne Ancien administrateur Points: 5488 Défis: 41 Message

Citer : Posté le 24/10/2013 19:13 | #


Parce que dans le tuto tu dis que les registres font 2 octets, donc c'est à corriger.
Je ne sais pas si t'as parlé de la convention qui veut que les registres r0-r7 sont à la charge de la fonction appelée, et r8-r14 à la charge de la fonction appelante, et donc à empiler-dépiler si on veut s'en servir.

Pour l'assembleur, je ne vois pas trop l'intérêt de le poster. On ne peut pas vraiment développer un truc sérieux avec un outil aussi simpliste.
Ziqumu Hors ligne Membre d'honneur Points: 3055 Défis: 9 Message

Citer : Posté le 24/10/2013 19:21 | #


Ah oui, en effet, merci Pierrotll.

Je ne l'ai pas formulé ainsi, mais oui je l'ai précisé
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


C'est vrai qu'un assembleur on-calc je ne vois pas trop l'interet, par contre ça pourrait être intéressant de le porter sur PC sachant qu'en plus j'ai fait un desassembleur.
Cartix Hors ligne Membre Points: 2748 Défis: 98 Message

Citer : Posté le 29/08/2014 13:21 | #


Bon, j'espère que je ne vais pas me faire taper dessus
Est-ce une bonne idée de remplacer ça :
SHAR    R0
SHAR    R0
SHAR    R0

Par ça :
.arepeat 3
SHAR    R0
.aendr

Et n'y a-t-il pas moyen d'optimiser ça ?
Ziqumu Hors ligne Membre d'honneur Points: 3055 Défis: 9 Message

Citer : Posté le 29/08/2014 23:59 | #


Ce qui est drole c'est que je m'y connais pas en syntaxe assembleur, je connais juste les instructions puisque j'ai principalement fait de la rétro-ingénierie et donc là t'as les instructions directement.

Je pense que remplacer le premier par le second ne changera rien au final, parce que une fois compilé, ça sera la même chose, c'est juste chez toi dans ton code, ça fait moins de répétition.
N’essaie pas de te focaliser sur le fait d'avoir un code propre en assembleur, c'est pas possible, y'a des opérations où il faut répéter plusieurs fois l'instruction, c'est comme ça que ça fonctionne, et les trois rassemblés durent qu'une fraction de seconde.

Regarde l'instruction SHLR2, elle pourraient réduire d'une instruction ton code, mais il me semble qu'il y a une nuance, vérifie avant de l’utiliser.
Lephenixnoir En ligne Administrateur Points: 24228 Défis: 170 Message

Citer : Posté le 10/11/2014 09:19 | #


J'ai eu un résultat suprenant avec un code (section .text, les fonctions en global etc) :
_getvalue1:
        rts
        mov.l    mysym, r0

_getvalue2:
        mov.l    mysym, r0
        rts
        mov.l    @r0, r0

.align 4

mysym:
        .long    0x300200


getvalue1() retourne 0xE200E40F, et getvalue2() retourne 0x4F22D331.
Le second correspond bien aux quatre premiers octets de code se trouvant à 0x300200 (code d'initialisation), mais le premier... qu'est-ce que c'est ?

Ajouté le 10/11/2014 à 10:06 :
Ok, je pense que j'ai compris. C'est assez bizarre :

Voici les symboles :
mysym:
        .long 0x300200

mysym2:
        .long mysym

Et voilà les résultats :
mov.l mysym,  r0 ! 0xE200E40F (inconnu)
mov.l @r0, r0    ! 0x4F22D331 (@mysym)
mov.l mysym2, r0 ! 0xE5097904 (inconnu)
mov.l @r0, r0    ! 0x00300200 (@mysym2=mysym)


Du coup, ça signifie qu'ayant :
s1:
        .long s2
On accède à la valeur de s1 en mettant s2 dans un registre, puis en cherchant la valeur à cette adresse :
mov.l s1, r0
mov.l @r0, r0 ! r0 = s2

Du coup, on dirait que c'est impossible d'accéder à la valeur s2 sans passer par s1, car on trouve une valeur aléatoire (je soupçonne que ce soit l'adresse du symbole).

Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Xavier59 Hors ligne Membre de CreativeCalc Points: 1337 Défis: 12 Message

Citer : Posté le 04/05/2016 16:21 | #


Tutoriel de qualité tombé dans les abysses du site
Franchement, très bien écrit et très intéressant ! Ce pont de 4 jours va me permettre de m'y coller et de comprendre tout ça un peu mieux en profondeur !
1337
Ninestars Hors ligne Membre Points: 2461 Défis: 24 Message

Citer : Posté le 29/09/2016 18:05 | #


J'ai dis que je le lirai en 2013. Désolé j'ai un peu de retard...
Super tuto merci
Yatis En ligne Membre Points: 580 Défis: 0 Message

Citer : Posté le 22/03/2018 15:58 | #


Je suis totalement un débutant en asm. J'ai essayer de deassembler la fonction PrintMini() et j'ai obtenu ça:
PrintMini(0, 0, "test", 0){
    CMP/PL r5
    BF
    MOV #h'3b, r2
    CMP/GT r2, r5
    BT
    CMP/PZ r4
    BT
    RTS
    MOV #h'00, r0
    MOV.l @(h'2D,PC), r3
    JUMP @r3
    NOP
    RTS
    NOP
    
}



J'ai à peu près compris le code mais je vois pas l'intérêt de mettre un NOP avant le dernier RTS... On attend d'arriver à l'address @r3 pour exit ? Et es-ce-que la chaine de caractère et "stocker" dans r0 (c'est pour ça où on met 0x00 dans r0)? (je dois avouer de n'avoir pas fini d'analyser tout à 100% donc j'aurais peut-être ma réponse après... : D)
Lephenixnoir En ligne Administrateur Points: 24228 Défis: 170 Message

Citer : Posté le 22/03/2018 16:03 | #


Il se trouve que jmp est une instruction qui possède un delay slot, comme rts. Le nop en question est donc exécuté avant le saut. S'il n'avait pas été là, la fonction aurait demandé au processeur d'exécuter rts avant de sauter, et forcément ça ne lui aurait pas plu (exception).

D'ailleurs, il te manque des trucs après tes bt et tes bf. Il devrait y avoir une destination.

Du reste, les conventions d'appel (règles qui définissent comment on passe les arguments et les valeurs de retour dans les fonctions) sont les suivantes :

- Les 4 premiers arguments sont placés dans r4, r5, r6, r7 ;
- S'il y a plus d'arguments, ils sont envoyés sur la pile (en ordre inverse) ;
- La valeur de retour est placée dans r0 ;
- Les registres r0 à r7 peuvent être modifiés librement par la fonction appelée ;
- La fonction appelée doit restaurer tous les autres registres après utilisation, si elles les utilise.

La première ligne (cmp/pl r5) fait donc référence au deuxième argument.

J'en dis pas plus, tu trouveras sans doute tout seul la suite...
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Yatis En ligne Membre Points: 580 Défis: 0 Message

Citer : Posté le 23/03/2018 14:10 | #


@Lephenixnoir Merci je viens de comprendre comment on sais le delay slot d'une instruction (c'est bien le nombre de cycle ?) j'ai donc essayer de comprendre comment la fonction est appeler:


quand on appelle PrintMini(8, 3, "test", 0){
    mov.l @(h'00c, pc), r6    (????) (indique l'adresse du début du mot "test" ?)
    mov #h'03, r5   (met 0x03 dans r5 (deuxième paramètre))[/green]
    mov.l @(h'00c, pc), r3  (????) (indique l'adresse de la fin du mot "test" ?)
    mov #h'00, r7   (met 0x00 dans r7 (quatrième paramètre))[/green]
    jsr @r3          (met PC dans PR et l'adresse de r3 dans PC (comprend paaas))
    mov #h'08, r4    (met 0x08 dans r4 (premier paramètre))
    bra h'1ffc   (je suppose qu'il vas a la fonction PrintMini en question seulement PC+1FF ne correspond pas a l'adresse de PrintMini()  )
    nop        (technique ancestral de glandage)
}


Du coup je comprend vraiment pas grand chose....surtout que mov.l @(h'00c, pc), r6 n'est pas possible car j'ai 0xD603 et que d’après la doc:

MOV.L  @(disp,PC),Rn    (disp + PC)→Rn        1101nnnndddddddd


Donc je devrait avoir mov.l @(h'03, pc), r6 non ?
et pourquoi ya un 'c' qui ce glisse dans les mov.l et dans le bra ? ça veux dire que le chiffre est négatif ?


(Effectivement après les BT et les BF ya bien une adresse... )

Ajouté le 23/03/2018 à 16:42 :
Aussi comment on fait pour "mélanger" C et ASM avec le fxSDK ? J'ai essayer d'utiliser asm(); et __asm__() mais impossible de compilé... (déso si je pose beaucoup de question).
Ou alors je doit faire un fichier .src (comme serial.src):

    .SECTION P,CODE,ALIGN=4

    .MACRO SYSCALL FUNO, SYSCALLNAME, TAIL=nop
    .export \SYSCALLNAME'
\SYSCALLNAME'
    mov.l #h'\FUNO, r0
    mov.l #H'80010070, r2
    jmp @r2
    \TAIL'
    .ENDM

    SYSCALL 040C,    _Serial_ReadByte
    SYSCALL 040D,    _Serial_ReadBytes
    SYSCALL 040E,    _Serial_WriteByte
    SYSCALL 040F,    _Serial_WriteBytes
    SYSCALL 0410,    _Serial_WriteByteFIFO
    SYSCALL 0411,    _Serial_GetRxBufferSize
    SYSCALL 0412,    _Serial_GetTxBufferFreeCapacity
    SYSCALL 0413,    _Serial_ClearReceiveBuffer
    SYSCALL 0414,    _Serial_ClearTransmitBuffer
    SYSCALL 0418,    _Serial_Open
    SYSCALL 0419,    _Serial_Close
    SYSCALL 0422,    _Serial_Peek
    SYSCALL 0425,    _Serial_IsOpen
    .end


dans ce cas la, que signifie:

.SECTION P,CODE,ALIGN=4
Lephenixnoir En ligne Administrateur Points: 24228 Défis: 170 Message

Citer : Posté le 23/03/2018 17:39 | #


Non, le delay slot, c'est une propriété qu'ont certaines instructions (en gros les sauts) qui fait que l'instruction suivante est exécutée avant elle, rien de plus.

Ton interprétation du code de l'appel est à peu près juste. Les mov @(..., pc) veulent dire qu'on va lire la valeur à une certaine distance à partir de la position actuelle (les données sont donc au milieu du code).

Hmm, pour le 03 tu as raison, mais wait - tu désassembles à la main ? Utilise un outil plutôt : maintenant que tu as compris le principe, en faire plus ne fait qu'augmenter le risque d'erreur.

Il n'y a pas d'assembleur inline dans le SDK (ou presque ; vois @Cakeisalie5 qui a dû trouver ça une fois), si tu veux bidouiller en assembleur il vaut mieux faire du GCC. Ce n'est pas urgent, pour l'instant tu devrais écrire ton code dans un fichier .src et le mettre dans des fonctions
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Yatis En ligne Membre Points: 580 Défis: 0 Message

Citer : Posté le 23/03/2018 19:19 | #


@Lephenixnoir désoler d’être extrêmement chiant a poser des question toute les deux minute mais j'ai essayer de faire un fichier .src mais le compilateur m'insulte un max et j'arrive pas a comprendre comment on le structure (j'ai essayer plusieurs manière mais impossible de faire un code qui compile). Du coup je me suis rappelé que ML_vram_adress() était du full assembleur j'ai donc essayer ça:

#include "main.h"
#define TST_print (*(rectoum)printTest)

typedef void (*rectoum)(int x, int y, const unsigned char* str, int type);
const unsigned int printTest[]={ 0x45118B04, 0xE23B3527, 0x89014411, 0x89010000B, 0xE000D32D, 0x432B0009, 0x000B0009 };


void main(){
    ML_clear_vram();
    TST_print(10, 10, "test", 0);
    ML_display_vram();
    while(1);
}


j'ai seulement fait un copier-coller du code que j'ai decompiler de PrintMini(). Comme la fonction revoie rien mais qu'elle a des paramètre j'ai mis un typedef void* (*rectoum)(int x, int y, const unsigned char* str, int type) sans vraiment comprendre pourquoi on fait ça mais comme ML_vram_adress() revoie une adresse est que lui il a besoins d'un typedef int (*text)(void) bah j'ai essayer...Maintenant ça compile mais ça n'affiche rien
Lephenixnoir En ligne Administrateur Points: 24228 Défis: 170 Message

Citer : Posté le 23/03/2018 23:33 | #


Au contraire, pose-en des questions, c'est probablement pour ton bien <3

Ouuuh là, tu peux pas faire ça. L'assembleur, ça doit être organisé et bien défini. En copiant du code comme ça, tu commets plusieurs erreurs :
- Tu casses possiblement les références de la forme @(disp, pc)
- Tu sors le code de son contexte de pile (!)
- Tu exécutes des données dans un programme C (pas tellement une erreur, plus du mauvais goût ici)

Pour programmer en assembleur sur le SDK, crée un fichier, disons asm.src, et ajoute-le aux fichiers sources du projet. Définissons par exemple la fonction int f(int x, int y) { return x + y; } :

  .export _f

_f:
  add r4, r5
  rts
  mov r5, r0

Tu peux voir que j'ai écrit une fonction, qui se termine par rts, et qui commence par un symbole (comme ça on peut y sauter, habile n'est-ce pas ?). J'ai exporté le symbole pour le rendre visible par l'extérieur : contrairement au C, par défaut les symboles sont « privés ».

Tu peux aussi voir que le nom de la fonction a été préfixé d'un underscore. C'est comme ça sur un certain nombre de plateformes ; la variable C truc est traduite en le symbole _truc. Il faut s'y faire.

Ensuite, dans ton programme C, tu peux faire...

extern int f(int x, int y);
int douze = f(4, 8);

Commence par écrire des fonctions innocentes de ce type, du genre strlen(), memcmp() et des trucs simples pour te faire la main sur le jeu d'instructions et la méthode de développement. Ça te placera dans une bonne position pour désassembler des trucs plus compliqués, appeler des sous-fonctions ou rerouter des interruptions.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Yatis En ligne Membre Points: 580 Défis: 0 Message

Citer : Posté le 24/03/2018 12:16 | #


Le compilateur refuse de compiler...


test.src(1) : C2500 (E) Illegal token "."

/*test.src*******************************************/
    .export _taya

    _taya:
        add r4, r5
        rts
        mov r5, r0
/****************************************************/

/*main.c*******************************************/
#include "main.h"
#include "test.src"
extern int taya(int x, int y);

void main(){
    int yata;
    yata = taya(9, 6);
    ML_clear_vram();
    PrintInt(0, 0, yata);
    ML_display_vram();
    while(1);
}
/***************************************************/


J'ai essayer d’enlever le '.' mais après c'est le bordel:

test.src(1) : C2500 (E) Illegal token "_taya"
test.src(5) : C2500 (E) Illegal token "rts"
main.c(3) : C2500 (E) Illegal token "extern"
main.c(3) : C2500 (E) Illegal token "int"


Du coup je sais vraiment pas quoi faire pour régler ça...
Zezombye Hors ligne Rédacteur Points: 1756 Défis: 13 Message

Citer : Posté le 24/03/2018 12:25 | #


Normal, tu fais #include "test.src" ce qui fait que le compilateur le traite comme du code C.

Il ne faut pas l'ajouter via un include, mais en le mettant dans les sources du projet (come monochromelib).
Divers jeux : Puissance 4 - Chariot Wars - Sokoban
Ecrivez vos programmes basic sur PC avec BIDE
Yatis En ligne Membre Points: 580 Défis: 0 Message

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)

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 72 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd