0% ont trouvé ce document utile (0 vote)
1 vues3 pages

TD5 Riscv

Ce document traite de l'ABI RISC-V et de l'utilisation d'une chaîne de compilation pour générer du code assembleur. Il explique comment configurer l'environnement de compilation, compiler des fonctions en C, et analyser le code assembleur généré, y compris la gestion de la pile et des variables globales. Enfin, il aborde l'édition de liens et la désassemblage du binaire résultant pour examiner les adresses des variables et des registres.
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
1 vues3 pages

TD5 Riscv

Ce document traite de l'ABI RISC-V et de l'utilisation d'une chaîne de compilation pour générer du code assembleur. Il explique comment configurer l'environnement de compilation, compiler des fonctions en C, et analyser le code assembleur généré, y compris la gestion de la pile et des variables globales. Enfin, il aborde l'édition de liens et la désassemblage du binaire résultant pour examiner les adresses des variables et des registres.
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

ARCHITURE DES MICROPROCESSEURS

TD5 : RISC-V Application Binary Interface


4. 1. Introduction
Dans ce dernier TD, nous allons explorer l'ABI RISC- V, afin de mieux appréhender le code généré par le compilateur et de
pouvoir suivre une session de debug d'un code assembleur. Ce qui est vu dans ce TD peut être extrapolé pour d'autres
architectures microprocesseurs (ARM , i686 x86-64 ...)

4. 2. Mise en place de la chaîne de compilation


La chaîne de compilation est déjà installée sur le serveur, il vous suffit d’ajouter à votre variable d’environnement « PATH » le
chemin vers les exécutables, pour ce faire exécuter la ligne de commande suivante dans un terminal :
$ source /soft/MENTOR/config_bashrc/.bashrc_riscv

4. 3. Le compilateur
Il s'agit d'une chaîne de compilation croisée, c'est à dire s'exécutant sur x86-64 mais produisant du code binaire RISC-V.
3. 1. Dans un terminal exécutez la commande suivante : riscv-none-embed-gcc --help
3. 2. Quelle option permet de :
 Lancer seulement le préprocesseur C ?
 De compiler et d'assembler le code source C sans faire d’édition de lien?
 De compiler seulement le code source C ?
 De spécifier le nom du fichier de sortie ?

3. 3. L’option –v --help permet d’avoir une aide plus détaillée.


Que permettent les options :
 -g
 -march
 -fverbose-asm
 -fomit-frame-pointer
 -mabi
(indication : utilisez | et grep pour filtrer la sortie de l’aide)

4. 4. Exploration du code assemblé et exploration de la structure de la pile


4. 1. Compilez seulement (voir question précédente) le programme suivant que vous placerez dans un fichier main.c. Le fichier
de sortie sera nommé main.s.

int fct1( int a, int b, int c, int d, int e, int f ,int g, int h )
{
return(a+b+c+d+e+f+g+h);
}

4. 2. De quel type de fonction s'agit-il ? (fonction feuille ou non, nombre de variables locales, nombre de paramètres en entrée
...)

4. 3. La réponse à la question qui précède va vous permettre d'identifier la structure que doit avoir la pile.
Pour compiler le code :
riscv-none-embed-gcc -mabi=ilp32 -march=rv32i main.c -S -fverbose-asm -fomit-frame-pointer
Fonction feuille :
 Absence de la zone de sauvegarde de paramètres puisqu’on n’appelle pas de fonction
 pas de sauvegarde de $ra
Pas de variables locales :
 Absence de la zone pour l’allocation des variables locales
A priori la fonction n’est pas complexe et ne doit pas nécessiter l’usage de registres à sauvegarder :
 Absence de la zone pour la sauvegarde des registres s0 à s11.
Donc, a priori il n’y a pas besoin d’allouer d’espace sur la pile pour l’exécution de la fonction fct1.
Cependant nous avons vu à la question 4.3 que sans optimisation de compilation, le compilateur utilise $fp systématiquement.

1
Pour éviter cet usage systématique il faut utiliser l’option : -fomit-frame-pointer .

fct1:
addi sp,sp,-32 #,,
sw a0,28(sp) # a, a
sw a1,24(sp) # b, b
sw a2,20(sp) # c, c
sw a3,16(sp) # d, d
sw a4,12(sp) # e, e On crée un espace de 32 octets sur
sw a5,8(sp) # f, f la pile qui correspond à l’espace
sw a6,4(sp) # g, g nécessaire pour stocker les huit
sw a7,0(sp) # h, h
# main.c:15: return(a+b+c+d+e+f+g+h); paramètres
lw a4,28(sp) # tmp80, a La taille de la pille est bien un
lw a5,24(sp) # tmp81, b
add a4,a4,a5 # tmp81, _1, tmp80 multiple de 16
# main.c:15: return(a+b+c+d+e+f+g+h);
lw a5,20(sp) # tmp82, c
add a4,a4,a5 # tmp82, _2, _1 Les huit premiers paramètres sont
# main.c:15: return(a+b+c+d+e+f+g+h);
lw a5,16(sp) # tmp83, d
passés par registres : a0..a7
add a4,a4,a5 # tmp83, _3, _2
# main.c:15: return(a+b+c+d+e+f+g+h);
lw a5,12(sp) # tmp84, e On effectue l’addition des huit
add a4,a4,a5 # tmp84, _4, _3 paramètres
# main.c:15: return(a+b+c+d+e+f+g+h);
lw a5,8(sp) # tmp85, f
add a4,a4,a5 # tmp85, _5, _4
# main.c:15: return(a+b+c+d+e+f+g+h); On passe le résultat dans a0
lw a5,4(sp) # tmp86, g
add a4,a4,a5 # tmp86, _6, _5
# main.c:15: return(a+b+c+d+e+f+g+h); On libère l’espace sur la pile
lw a5,0(sp) # tmp87, h
add a5,a4,a5 # tmp87, _15, _6 On retourne à l’appelant
# main.c:16: }
mv a0,a5 #, <retval>
addi sp,sp,32 #,,
jr ra #
.size fct1, .-fct1

4. 4.
Dessinez la zone mémoire de la pile : les adresses du bas de la pile au haut de la pile et leur contenu.

adresse contenu
sp+28
sp+24
sp+20
sp+16
sp+12
sp+8
sp+4
sp

4. 5. Ajoutez la fonction suivante au fichier main.c :


int fct2( int a, int b, int c, int d, int e, int f ,int g, int h,int i,int j)
{
int x;
int y;

x=a+b;
y=i+j;

return(a+b+c+d+e+f+g+h+i+j+x+y);
}
Mêmes questions 1 à 3 que précédemment. Faites apparaître les « frame » des fonctions (appelante et appelée)

2
4. 6. De nouveau complétez votre code main avec la fonction suivante et dé-commentez l’appel à la fonction fct1 :
#include <alloca.h>
void fct3(unsigned char i){
unsigned int *ptr;

ptr=alloca(4);
*ptr=fct2(1,2,3,4,5,6,7,8,9,10);

}
Comment est allouée la variable ptr ?
Quel va être la structure de la pile lors de l’appel de cette fonction ?

4. 7. De nouveau complétez votre code main :

int global[128];

int long main()


{
int A=1;
int B=2;
int C=3;
int D=4;
int E=5;
int F=6;
int G=7;
int H=8;
int I=9;
int J=10;

global[10]=fct2(0,1,2,3,4,5,6,7,8,9);
return(0);
}

Examinez le code assembleur qui permet d'accéder à la variable global , commentez.

5 Edition de liens :
Ajoutez la déclaration de la variable globale suivante dans main.c : int initialised_global=0x5555AAAA;
Récupérez les fichiers : [Link] et crt.s.
Assemblez le c runtime :
riscv-none-embed-gcc -mabi=ilp32 -march=rv32i -fomit-frame-pointer -c -x assembler crt.s -o crt.o

Compilez et assemblez le main.c :


riscv-none-embed-gcc -mabi=ilp32 -march=rv32i -c main.c -fverbose-asm -fomit-frame-pointer -o main.o

Construisez le binaire résultant :


riscv-none-embed-ld -T [Link] crt.o main.o -Map [Link] -o [Link]

Désassemblez le binaire résultant : riscv-none-embed-objdump -SD [Link] > main.s

5.1 Examinez [Link] , à quelles adresses se trouve les variables globales : global et initialized_global ?
5.2 Examinez main.s :
 à quelles valeurs sont initialisés les registres $sp et $gp ?
 à quelle adresse se trouvent le c runtime, la fonction main ?

Vous aimerez peut-être aussi