Posté le 27/11/2019 20:14
Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 162 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
Citer : Posté le 27/11/2019 20:34 | #
Pour faire de l'assembleur inline en C, ce n'est pas biens compliqué, en gros ça se résume a : __asm__ volatile ("sleep") (pour faire simple).
Pour faire un appel système en assembleur inline :
__asm__ volatile(
"mov.l %0, r1;"
"mov.l %1, r0;"
"jmp @r1;"
"nop"
:
: "r"(0x80010070), "r"(syscall_number)
: "r1", "r0"
);
Il faut savoir que la gestion des appels système se fait à l'adresse 0x80010070 SAUF pour les calculatrices couleurs ou ça se trouve à 0x80020070. Ceci dit la procédure reste la même le "numéro" du syscall doit être passé via le registre r0.
Citer : Posté le 27/11/2019 20:38 | #
Salut ! Les syscalls depuis le C c'est mal, il faut vraiment les faire en assembleur. Mais c'est pas dur !
Il y a plein d'exemples ici : https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/compat/src/core/syscalls.S
Essentiellement, les ingrédients suivants sont requis pour implémenter un syscall que tu appelles mycall() :
• .global _mycall expose le symbole (c'est le contraire de static en C)
• Il y a un bout constant qui est le code de la fonction, qui ne fait qu'appeler le syscall. Je l'ai mis dans une macro, mais tu peux l'écrire directement, auquel cas ça donne ça :
mov.l syscall_table, r2
mov.l 1f, r0
jmp @r2
nop
1: .long <ID du syscall>
• La ligne syscall_table: .long 0x80010070 à la fin (une seule fois pour tout le fichier, contrairement aux deux autres ingrédients qui sont une fois par syscall).
Ensuite tu mets ça dans un fichier .S, tu compiles avec GCC et pouf tout marche. Il n'y pas d'autre façon de faire, les "méthodes d'appel de syscall en C" qui existent font exactement ça mais de façon moche avec des fonctionnelles.
Si tu es sur Graph 90+E, c'est pareil mais l'adresse de la table des syscalls est différente et les numéros sont différents aussi.
Ajouté le 27/11/2019 à 20:39 :
J'ajoute que pour la solution de Yatis tu as absolument besoin d'avoir une fonction "naked" ou équivalente, sinon tu pourris le stack frame de GCC et c'est le crash assuré au retour du syscall
Citer : Posté le 27/11/2019 20:45 | #
Ha oui ! J'ai oublié de le préciser ! Du coup si tu veux un appel système au plein milieu d'une fonction tu peux faire :
__asm__ volatile (
"sts.l pr, @-r15"
"mov.l %0, r1;"
"mov.l %1, r0;"
"jsr @r1;"
"nop;"
"lds.l @r15+, pr"
:
: "r"(0x80010070), "r"(syscall_number)
: "r1", "r0"
);
Citer : Posté le 27/11/2019 22:12 | #
Il y a plein d'exemples ici : https://gitea.planet-casio.com/Lephenixnoir/gint/src/branch/compat/src/core/syscalls.S
Justement c'est exactement ça que j'ai essayé de recopier x), je me suis dit que gint devait forcement utiliser des syscalls du coup j'ai cherché dans le code et bingo j'avais compilé avec sh-elf-gcc -c syscalls.S, mais après pour link etc j'ai pas réussi et en déassemblant j'ai remarqué que le code n'était même pas dans le fichier (wtf ?), du coup j'ai regardé les fichiers du build de gint (build/src/core/syscalls.S.o) et en déassemblant celui ci j'ai trouvé la fonction syscall.
J'ajoute que pour la solution de Yatis tu as absolument besoin d'avoir une fonction "naked" ou équivalente, sinon tu pourris le stack frame de GCC et c'est le crash assuré au retour du syscall
Merci, je n'étais absolument pas au courant de ça, même de la notion "naked" (d'ailleurs vous pouvez m'expliquer svp ?)
Ha oui ! J'ai oublié de le préciser ! Du coup si tu veux un appel système au plein milieu d'une fonction tu peux faire :
__asm__ volatile (
"sts.l pr, @-r15"
"mov.l %0, r1;"
"mov.l %1, r0;"
"jsr @r1;"
"nop;"
"lds.l @r15+, pr"
:
: "r"(0x80010070), "r"(syscall_number)
: "r1", "r0"
);
Ah d'accord je pensais que avec le SH4 l'implémentation n'était plus la même que avec du amd64.
Merci pour vos réponses, je vais tenter de mettre ça en oeuvre.
Ajouté le 27/11/2019 à 22:17 :
Essentiellement, les ingrédients suivants sont requis pour implémenter un syscall que tu appelles mycall() :
• .global _mycall expose le symbole (c'est le contraire de static en C)
• Il y a un bout constant qui est le code de la fonction, qui ne fait qu'appeler le syscall. Je l'ai mis dans une macro, mais tu peux l'écrire directement, auquel cas ça donne ça :
mov.l syscall_table, r2
mov.l 1f, r0
jmp @r2
nop
1: .long <ID du syscall>
• La ligne syscall_table: .long 0x80010070 à la fin (une seule fois pour tout le fichier, contrairement aux deux autres ingrédients qui sont une fois par syscall).
Ensuite tu mets ça dans un fichier .S, tu compiles avec GCC et pouf tout marche. Il n'y pas d'autre façon de faire, les "méthodes d'appel de syscall en C" qui existent font exactement ça mais de façon moche avec des fonctionnelles.
Pour call le syscall en C on déclare la fonction ou le linker se charge de ça (j'essayerais demain de toute facon)
Citer : Posté le 27/11/2019 22:21 | #
Une fois que c'est assemblé tu as un fichier objet qui se linke normalement. Je ne sais pas trop quelle erreur tu as pu avoir. Le code désassemblé n'est pas forcément identique car la traduction assembleur texte → binaire n'est pas si simple, l'assembleur texte offre plein de facilités qui ne sont pas présentes dans le binaire (eg les labels) et qu'il n'est pas forcément possible de "recréer" quand tu désassembles.
La fonction syscall n'existe pas, c'est juste une macro. Partout où j'ai écrit syscall, le préprocesseur le remplace par les 5 lignes que je t'ai indiquées
En gros quand tu écris une fonction en C, même vide, il y a du code derrière, pour créer le stack frame (bloc de mémoire sur la pile contenant les variables locales et divers trucs utiles). Ça s'appelle le prologue. À la fin de la fonction, il y a du code qui détruit le stack frame, appelé épilogue.
Ici, pour une raison que je ne détaille pas (optimisation des appels terminaux), le code original de Yatis s'échappe de la fonction, de telle façon que quand le syscall se termine il ne repasse pas dans la fonction C, mais retourne direct à l'appelant pour aller plus vite. Donc en gros, dans cet exemple :
{
__asm__ volatile ( /* etc */ );
}
int f(int x)
{
return x + mon_syscall();
}
Quand tu appelles f(), cela appelle mon_syscall(), qui appelle le syscall. Dans le modèle "normal", une fois le syscall terminé on revient dans mon_syscall() puis le résultat remonte dans f(). À cause de la façon dont le code de Yatis s'échappe, une fois le syscall fini il va revenir direct à f(). Ce comportement est "intelligent" parce que mon_syscall() sert juste à appeler le syscall, une fois que c'est fini il n'y a plus rien à faire.
... en principe. Parce que là on a exécuté le prologue de mon_syscall() mais pas l'épilogue. Ce qui peut tout casser.
Une fonction "naked" est une fonction qui n'a ni prologue ni épiloque, ce qui est prévu pour ce genre de cas.
Et elle l'est ! Tu ne dervrais pas avoir de doutes là-dessus. L'assembleur est différent, le code binaire est différent, les conventions d'appels sont différentes, ici l'OS est différent de Linux ce qui a aussi une influence. Si tu n'est pas sûr de pourquoi il est juste impossible que l'implémentation soit la même, convaincs-toi avant de passer à la suite !
Le linker ne sait pas ce qu'est le C, c'est donc à toi de rajouter le bon prototype dans un header.
Citer : Posté le 27/11/2019 22:50 | #
Une fois que c'est assemblé tu as un fichier objet qui se linke normalement. Je ne sais pas trop quelle erreur tu as pu avoir. Le code désassemblé n'est pas forcément identique car la traduction assembleur texte → binaire n'est pas si simple, l'assembleur texte offre plein de facilités qui ne sont pas présentes dans le binaire (eg les labels) et qu'il n'est pas forcément possible de "recréer" quand tu désassembles.
En déassemblant avec IDA pro je n'ai pas trouvé de code (0 ligne) il y avait que les labels globals, alors que avec le fichier compilé par make il y avait du code.
Et oui je suis bien conscient que après assemblage le code ne sera pas identique ça c'est sûr, pareil quand on comile du C on trouvera pas le texte identique au désassemblage (d'ailleurs on ne verra uniquement des instructions qui, pourront être différente en fonction de l'architecture cible ainsi que du niveau de compilation et même du compilateur).
Mais je pense que je me suis mal exprimé quand j'ai voulu expliquer mon problème de compilation, en gros en compilant à la main je n'ai pas obtenu le même fichier objet que make (j'ai dû rater un argument ou autre)
La fonction syscall n'existe pas, c'est juste une macro. Partout où j'ai écrit syscall, le préprocesseur le remplace par les 5 lignes que je t'ai indiquées
Ça je l'avais remarqué. Je programme depuis un bon moment ce genre de notions de bases je connais bien
Quand tu appelles f(), cela appelle mon_syscall(), qui appelle le syscall. Dans le modèle "normal", une fois le syscall terminé on revient dans mon_syscall() puis le résultat remonte dans f(). À cause de la façon dont le code de Yatis s'échappe, une fois le syscall fini il va revenir direct à f(). Ce comportement est "intelligent" parce que mon_syscall() sert juste à appeler le syscall, une fois que c'est fini il n'y a plus rien à faire.
... en principe. Parce que là on a exécuté le prologue de mon_syscall() mais pas l'épilogue. Ce qui peut tout casser.
Aaaah j'ai bien compris c'est bon merci.
Et si vous pouvez détailler si vous voulez bien.
La grosse différence cest le jmp et jsr, jsr va démarrer une subroutine (une autre fonction, donc jsr va push sur la pile l'adresse de return et va jmp ensuite dans le syscall pour que quand le return du syscall est appelé on retourne dans la fonction d'avant) alors que avec jmp quand on est dans le syscall c'est comme si on était encore dans la fonction car rien est push dans la pile donc l'or du return du syscall dans la pile l'adresse de retour sera la fonction avant la fonction qui a jump dans le syscall.
(Dites moi si j'ai mal compris ou que je m'exprime mal car dans ma tête c'est très claire, mais j'ai dû mal a expliquer ce genre de choses à l'écrit/oral)
Ajouté le 27/11/2019 à 22:57 :
Et elle l'est ! Tu ne dervrais pas avoir de doutes là-dessus. L'assembleur est différent, le code binaire est différent, les conventions d'appels sont différentes, ici l'OS est différent de Linux ce qui a aussi une influence. Si tu n'est pas sûr de pourquoi il est juste impossible que l'implémentation soit la même, convaincs-toi avant de passer à la suite !
On s'est mal compris je crois, je parlais de l'implémentation de __asm__ volatile non pas de la synthaxe de l'assembleur (SH4 est vraiment différent de amd64 en effet)
Ajouté le 27/11/2019 à 22:58 :
Le linker ne sait pas ce qu'est le C, c'est donc à toi de rajouter le bon prototype dans un header.
Pas de problème je me chargerais de ça
En tout cas merci pour toutes ces réponses je metterais ça en pratique demain
Citer : Posté le 28/11/2019 09:07 | #
Ce qui est encore différent d'ailleurs car le C est vraiment compilé. L'assembleur, dans les cas les plus basiques, est vraiment traduit de façon bijective, on change la méthode d'écriture et c'est tout. Tu pourrais écrire de l'assembleur qui se désassemble exactement en son code source. Ce serait juste hyper casse-pieds parce que y'aurait pas de labels, pas de macros, rien.
Exactement, le principe c'est précisément ça ! La seule différence avec ton explication, c'est que sur SuperH, lors d'un appel de fonction l'adresse de retour est placée dans pr au lieu d'être mise sur la pile, et c'est la fonction appelée qui la sauvegarde plus tard sur la pile si elle veut faire des sous-appels (sts.l pr, @-r15).
Cette optimisation des appels terminaux est très utile dans les langages fonctionnels pour éliminer la récursion... ce que tu connais peut-être d'ailleurs
C'est sympa de faire un peu de technique par ici. o/
Citer : Posté le 28/11/2019 14:07 | #
Exactement, le principe c'est précisément ça ! La seule différence avec ton explication, c'est que sur SuperH, lors d'un appel de fonction l'adresse de retour est placée dans pr au lieu d'être mise sur la pile, et c'est la fonction appelée qui la sauvegarde plus tard sur la pile si elle veut faire des sous-appels (sts.l pr, @-r15).
Cette optimisation des appels terminaux est très utile dans les langages fonctionnels pour éliminer la récursion... ce que tu connais peut-être d'ailleurs
C'est sympa de faire un peu de technique par ici. o/
Oui et r15 est utilisé comme un pointeur de pile par convention (d'où les @+r15 et @-r15) (de ce que j'ai lu)
Oui je vois ce que veut dire recursion
Et oui c'est intéressant de faire de la technique
Ajouté le 28/11/2019 à 17:19 :
UPDATE : Bon je viens de découvrir pourquoi je en retrouvais pas le code dans le fichier objet... (vous allez être déçu de mon erreur x))
J'avais oublié de define FX9860G dans les options de préprocesseur... donc il n'y avait pas de code (logique)
Ajouté le 28/11/2019 à 17:20 :
Je m'en suis rendu compte en tapant sh-elf-gcc -E syscalls.S -o syscalls.S.o
(-E sers à appliquer uniquement le préprocesseur)
Citer : Posté le 28/11/2019 17:36 | #
Ah oui, désolé pour le define de gint, je me fais avoir aussi... xD
Citer : Posté le 28/11/2019 17:39 | #
Ah oui, désolé pour le define de gint, je me fais avoir aussi... xD
C'est pas le genre de trucs qu'on regarde en premier quand ça fait ce genre d'erreur c'est assez long à trouver des bugs aussi simple xD
Ajouté le 28/11/2019 à 18:21 :
Une question, pourquoi dans syscalls.S il n'y a pas d'align 4 pour syscall_table ?
Citer : Posté le 28/11/2019 18:49 | #
Tout est déjà aligné à 4 octets structurellement, mais c'est vrai que dans l'absolu il en faudrait un.
Citer : Posté le 28/11/2019 19:00 | #
Tout est déjà aligné à 4 octets structurellement, mais c'est vrai que dans l'absolu il en faudrait un.
D'accord, en fait c'est parce que je n'ai pas encore la notion des mov.l constant value (le mov.l PC-relative load)
Ajouté le 28/11/2019 à 19:45 :
Petite question, pourquoi .pretext en tant que section ?
Citer : Posté le 28/11/2019 19:49 | #
Pour s'assurer que le code soit accessible même si l'add-in ne peut pas être complètement mappé. Essentiellement gint abandonne l'OS à un certain point et perd donc la capacité de mapper des pages de l'add-in (je ne veux pas le faire à la main, car en cas de crash on peut bricker une calto). Donc il y a des situations où l'add-in peut tourner mais seulement une partie du code est accessible. Mettre les parties cruciales du code (gestion CPU, détection de la plateforme, rendu de texte) au début permet de s'assurer que ce code est toujours disponible même dans les situations tordues. Ça me permet de détecter et afficher les erreurs à tout prix. Mais c'est vraiment des cas rares.
Citer : Posté le 28/11/2019 20:51 | #
Pour s'assurer que le code soit accessible même si l'add-in ne peut pas être complètement mappé. Essentiellement gint abandonne l'OS à un certain point et perd donc la capacité de mapper des pages de l'add-in (je ne veux pas le faire à la main, car en cas de crash on peut bricker une calto). Donc il y a des situations où l'add-in peut tourner mais seulement une partie du code est accessible. Mettre les parties cruciales du code (gestion CPU, détection de la plateforme, rendu de texte) au début permet de s'assurer que ce code est toujours disponible même dans les situations tordues. Ça me permet de détecter et afficher les erreurs à tout prix. Mais c'est vraiment des cas rares.
En gros c'est le code à mettre au début du add-in ?
Citer : Posté le 28/11/2019 20:53 | #
Ah ! Oui, la section .pretext est la première dans la mémoire virtuelle (donc dans le fichier g1a, par la force des choses). Ça se voit aisément dans le linker script.
Citer : Posté le 28/11/2019 20:54 | #
Ah ! Oui, la section .pretext est la première dans la mémoire virtuelle (donc dans le fichier g1a, par la force des choses). Ça se voit aisément dans le linker script.
Le linker script ? C'est le fichier .ld non ?
Citer : Posté le 28/11/2019 20:55 | #
C'est ça, aussi noté .x dans GCC.
Citer : Posté le 28/11/2019 20:57 | #
C'est ça, aussi noté .x dans GCC.
D'acccccord c'est bien foutu tout ça, et j'ai réussi pour les syscalls, j'ai récupéré l'OS version sur 10 bytes ça a bien marché
Citer : Posté le 28/11/2019 20:58 | #
Parfait ! Bon courage pour la suite
Citer : Posté le 28/11/2019 21:04 | #
Parfait ! Bon courage pour la suite
Est-il possible de communiquer entre la calculette et le PC ? (avec l'USB et avec le 3-Broches)
Je suppose que oui.
Donc on pourrait créer un client TCP théoriquement si on crée un protocole entre la calculette et le PC ?
(c'est très théorique)