Merge branch 'thread_scheduler' of https://gitlab.istic.univ-rennes1.fr/simpleos/burritos into thread_scheduler

This commit is contained in:
amaury 2023-03-13 18:48:32 +01:00
commit edf52a7b63
76 changed files with 9481 additions and 416 deletions

3
.gitignore vendored
View File

@ -1 +1,4 @@
/target
/.idea
*.iml
/*.txt

27
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,27 @@
default:
image: rust:latest
stages:
- build
- test
build-job:
stage: build
script:
- echo "Compiling the code..."
- cargo build
- echo "Compile complete."
unit-test-job:
stage: test
script:
- echo "Running unit tests..."
- cargo test
lint-test-job:
only:
- merge_requests
stage: test
script:
- echo "Linting code..."
- cargo clippy

7
Cargo.lock generated
View File

@ -6,16 +6,9 @@ version = 3
name = "burritos"
version = "0.1.0"
dependencies = [
"lazy_static",
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"

View File

@ -4,5 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
lazy_static = "1.4.0"
libc = { version = "0.2.139", features = ["extra_traits"] }

View File

@ -1,3 +1,5 @@
![BurritOS Logo](assets/logo/logo_full.png)
# BurritOS
BurritOS (BurritOS Using Rust Really Improves The Operating System) is an educational operating system written in Rust and running on a RISC-V emulator. It aims to be used as an educational platform for learning about operating systems.

104
assets/logo/README.md Normal file
View File

@ -0,0 +1,104 @@
# BurritOS logo
Designed with Inkscape.
Font: [Fira Code](https://github.com/tonsky/FiraCode) by The Fira Code contributors
Logo by François AUTIN
## Licences
### Fira Code
Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
assets/logo/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

377
assets/logo/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 328 KiB

BIN
assets/logo/logo_128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/logo/logo_full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

5986
assets/logo/logo_full.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 447 KiB

196
doc/DOCUMENTATION.md Normal file
View File

@ -0,0 +1,196 @@
# Comment utiliser la documentation Rust
![](https://www.rust-lang.org/static/images/rust-social-wide.jpg)
## Écrire un commentaire en Rust
Pour écrire un commentaire qui apparaitra sur la doc de cargo, écrivez :
```rust
/// Votre commentaire
```
pour commenter votre fonction, attribut, structure, module....
Pour les commentaires de **crate**, utilisez :
```rust
//! Votre commentaire
```
*Les commentaires style java (`/** */`) sont supporté à l'exception des règles `@` **mais utilisez plutôt les commentaires standard de rust please***.
Vous pouvez ensuite écrire votre commentaire comme bon vous semble; la documentation supporte le markdown, n'hésitez surtout pas à mettre des h3, des textes den gras, des exemple encapsulé, etc.
Petit aide avec la markdown dans le chapitre [#aide-markdown](#aide-markdown).
### Exemple
Vous obtenez quelque chose sur votre code comme à la figure 1.
![Capture d'écran de main.rs](Screenshot_20221019_153737.png "Aperçu du main")
## Générer la documentation Rust
Éxecutez la commande __`cargo doc`__ dans le dossier du projet, cela va générer un dossier dans le dossier **target/doc/** qui contient des pages web, ouvrez le fichier **target/doc/burritos/all.html**.
Lors de l'affichage de la liste de vos fonctions et autres éléments, la doc **n'affiche que la première ligne**, le autres lignes sont affiché lorsqu'on ouvre les détails.
### Exemple
En partant du code de la figure 1, vous obtenez une documentation comme aux figures 2 et 3.
!["Page d'accueil de la doc"](Screenshot_20221019_153803.png "Page all.html de la crate burritos généré à partir de l'exemple de la figure 1")
!["Page de la fonction main de la doc"](Screenshot_20221019_153817.png "Page de la fonction main")
## Aide markdown
Comme le markdown est utile pour écrire et mettre en page des commentaire en Rust, petit tuto sur comment formaté du texte en markdown.
lorsque vous voulez passer à la ligne, faites 2 sauts à la ligne en markdown (oui pas une blague), un saut à la ligne sur le fichier markdown n'est pas
considérer comme un passage à la ligne, ça vous permet juste d'écrire des commentaires sans aller trop à droite dans votre IDE comme celui-ci.
### Italic et gras
```md
*Texte en italic* _Texte en italic_
**Texte en gras** __Texte en gras__
***Texte en italic et gras***
```
### Titre et sous-titres
```md
# Titre type h1
## Titre type h2
etc. jusqu'à
###### Titre type h6
```
### Listes
```md
- Une
- Liste
- Non
- Ordonnée
1. Une
2. Liste
3. Ordonnée
- [x] Liste
- [ ] de
- [ ] tâches
```
#### Exemple
- Une
- Liste
- Non
- Ordonnée
1. Une
2. Liste
3. Ordonnée
- [x] Liste
- [ ] de
- [ ] tâches
### Code
```md
`Commentaire mono-ligne`
```
```md
```
Commentaire
Multi
ligne
```
```
```md
```md
Commentaire
multi
ligne
avec
coloration
syntaxique (ici md pour markdown)
```
```
#### Exemple
Texte avant `Commentaire mono-ligne` texte après
```
Commentaire
multi
ligne
```
### Tableaux
```md
| Colonne 1 | Colonne 2 |
|-----------|-----------|
| ligne 1 | ligne 1 |
| ligne 2 | ligne 2 |
```
#### Exemple
| Colonne 1 | Colonne 2 |
|-----------|-----------|
| ligne 1 | ligne 1 |
| ligne 2 | ligne 2 |
### Liens et images
```md
[Mon lien](www.google.com)
![Mon image sur internet](www.imagelink.com/image.png)
![Mon image stocké sur mon pc](image/monscreenshot.png)
```
## Faire des tests
Pour écrire vos tests faites comme dans l'exemple ci-dessous:
![Exemple de test](Screenshot_20221019_173551.png)
Executez ensuite les tests en faisant `cargo test`, vous obtenez comme en suivant l'exemple ci-dessus le résultat de la figure 5.
![Exemple de résultat des tests](Screenshot_20221019_174036.png)
Lors d'un cargo build optimisé (`cargo build --release`), les tests ne sont pas inclus dans le fichier binaire.
## Liens utiles
La documentation officielle du crate core: [https://doc.rust-lang.org/core/index.html](https://doc.rust-lang.org/core/index.html)
La documentation officielle du crate std: [https://doc.rust-lang.org/std/index.html](https://doc.rust-lang.org/std/index.html)
La documentation des crates téléchargeable sur [https://crates.io/](https://crates.io/) est retrouvable sur: [https://docs.rs/](https://docs.rs/)
Pour le markdown vous pouvez retrouver des tutos ici: [https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html) et ici [https://www.markdownguide.org/cheat-sheet/](https://www.markdownguide.org/cheat-sheet/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

143
src/kernel/elf.rs Normal file
View File

@ -0,0 +1,143 @@
//Declaration des alias
/*
Def ELF :
The header file <elf.h> defines the format of ELF executable binary
files. Amongst these files are normal executable files, relocatable
object files, core files and shared libraries.
An executable file using the ELF file format consists of an ELF header,
followed by a program header table or a section header table, or both.
The ELF header is always at offset zero of the file. The program
header table and the section header table's offset in the file are
defined in the ELF header. The two tables describe the rest of the
particularities of the file
*/
/* Type for a 16-bit quantity. */
type Elf32_Half = u16;
type Elf64_Half = u16;
/* Types for signed and unsigned 32-bit quantities. */
type Elf32_Word = u32;
type Elf32_Sword = i32;
type Elf64_Word = u32;
type Elf64_Sword = i32;
/* Types for signed and unsigned 64-bit quantities. */
type Elf32_Xword = u64;
type Elf32_Sxword = i64;
type Elf64_Xword = u64;
type Elf64_Sxword = i64;
/* Type of addresses. */
type Elf32_Addr = u32;
type Elf64_Addr = u64;
/* Type of file offsets. */
type Elf32_Off = u32;
type Elf64_Off = u64;
//role de ce truc ?
const EI_NIDENT : u8 = 16;
//ELF file header 32 bits
struct Elf32Ehdr{
e_ident : [u8;EI_NIDENT],//16 octects décrivant comment le fichier doit etre parsé
//e_ident must starts with magice number : 0x 7f 45 4c 46
e_type : Elf32_Half,//type of the file
e_machine : Elf32_Half,//type architecture machine
e_version : Elf32_Word,//always 1
e_entry : Elf32_Addr,//entry point @ for executable
e_phoff : Elf32_Off,//Offset of the program header table
e_shoff : Elf32_Off,//Offset of the section header table
e_flags : Elf32_Word,//des flags ?
e_ehsize : Elf32_Half,//size of this (the header), redundant
e_phentsize : Elf32_Half,//size per program header
e_phnum : Elf32_Half,//number of program header
e_shentsize : Elf32_Half,//size per section header
e_shnum : Elf32_Half,//number of section header
e_shstrndx : Elf32_Half//section header string table index
}
//ELF file header 64 bits
//les champs ont le meme rôle que dans le header 32 bits
struct Elf64Ehdr{
e_ident : [u8;EI_NIDENT],
e_type : Elf64_Half,
e_machine : Elf64_Half,
e_version : Elf64_Word,
e_entry : Elf64_Addr,
e_phoff : Elf64_Off,
e_shoff : Elf64_Off,
e_flags : Elf64_Word,
e_ehsize : Elf64_Half,
e_phentsize : Elf64_Half,
e_phnum : Elf64_Half,
e_shentsize : Elf64_Half,
e_shnum : Elf64_Half,
e_shstrndx : Elf64_Half
}
/* e_ident offsets */
const EI_MAG0 : u32 = 0;
const EI_MAG1 : u32 = 1;
const EI_MAG2 : u32 = 2;
const EI_MAG3 : u32 = 3;
const EI_CLASS : u32 = 4;
const EI_DATA : u32 = 5;
const EI_VERSION : u32 = 6;
const EI_PAD : u32 = 7;
/* e_ident[EI_CLASS] */
const ELFCLASSNONE : u32 = 0;
const ELFCLASS32 : u32 = 1;
const ELFCLASS64 : u32 = 2;
/* e_ident[EI_DATA] */
const ELFDATANONE : u32 = 0;
const ELFDATA2LSB : u32 = 1;
const ELFDATA2MSB : u32 = 2;
/* e_type */
const ET_NONE : u32 = 0; /* No file type */
const ET_REL : u32 = 1; /* Relocatable file */
const ET_EXEC : u32 = 2; /* Executable file */
const ET_DYN : u32 = 3; /* Shared object file */
const ET_CORE : u32 = 4; /* Core file */
const ET_LOPROC : u32 = 0xff00; /* Processor-specific */
const ET_HIPROC : u32 = 0xffff; /* Processor-specific */
/* e_machine */
const EM_NONE : u32 = 0; /* No machine */
const EM_M32 : u32 = 1; /* AT&T WE 32100 */
const EM_SPARC : u32 = 2; /* SPARC */
const EM_386 : u32 = 3; /* Intel 80386 */
const EM_68K : u32 = 4; /* Motorola 68000 */
const EM_88K : u32 = 5; /* Motorola 88000 */
const EM_860 : u32 = 7; /* Intel 80860 */
const EM_MIPS : u32 = 8; /* MIPS R3000 */
const EM_RISC : u32 = 243; /* RISCV */
/* e_version */
const EV_NONE : u32 = 0; /* invalid version */
const EV_CURRENT : u32 = 1; /* current version */

View File

@ -1,31 +1,31 @@
/// Error enum, use it with Result<YourSucessStruct, **ErrorCode**>
pub enum ErrorCode {
INC_ERROR,
OPENFILE_ERROR,
EXEC_FILE_FORMAT_ERROR,
OUT_OF_MEMORY,
IncError,
OpenfileError,
ExecFileFormatError,
OutOfMemory,
OUT_OF_DISK,
ALREADY_IN_DIRECTORY,
INEXIST_FILE_ERROR,
INEXIST_DIRECTORY_ERROR,
NOSPACE_IN_DIRECTORY,
NOT_A_FILE,
NOT_A_DIRECTORY,
DIRECTORY_NOT_EMPTY,
INVALID_COUNTER,
OutOfDisk,
AlreadyInDirectory,
InexistFileError,
InexistDirectoryError,
NospaceInDirectory,
NotAFile,
NotADirectory,
DirectoryNotEmpty,
InvalidCounter,
/* Invalid typeId fields: */
INVALID_SEMAPHORE_ID,
INVALID_LOCK_ID,
INVALID_CONDITION_ID,
INVALID_FILE_ID,
INVALID_THREAD_ID,
InvalidSemaphoreId,
InvalidLockId,
InvalidConditionId,
InvalidFileId,
InvalidThreadId,
/* Other messages */
WRONG_FILE_ENDIANESS,
NO_ACIA,
WrongFileEndianess,
NoAcia,
NUMMSGERROR /* Must always be last */
}

View File

@ -4,3 +4,5 @@ pub mod scheduler;
pub mod mgerror;
pub mod system;
mod ucontext;
mod synch;
mod thread_manager;

View File

@ -1,5 +1,5 @@
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
pub struct Process {
pub num_thread: usize,
}

View File

@ -1,12 +1,14 @@
use std::sync::Arc;
use std::cell::RefCell;
use std::rc::Rc;
use crate::utility::list::List;
use crate::kernel::thread::Thread;
use super::system::{G_CURRENT_THREAD, G_THREAD_TO_BE_DESTROYED};
use super::system::System;
#[derive(PartialEq)]
pub struct Scheduler {
ready_list: List<Arc<Thread>>
ready_list: List<Rc<RefCell<Thread>>>
}
impl Scheduler {
@ -27,7 +29,7 @@ impl Scheduler {
/// ## Pamameter
///
/// **thread** is the thread to be put on the read list
pub fn ready_to_run(&mut self, thread: Arc<Thread>) {
pub fn ready_to_run(&mut self, thread: Rc<RefCell<Thread>>) {
self.ready_list.push(thread);
}
@ -37,7 +39,7 @@ impl Scheduler {
/// Thread is removed from the ready list.
///
/// **return** Thread thread to be scheduled
pub fn find_next_to_run(&mut self) -> Option<Arc<Thread>> {
pub fn find_next_to_run(&mut self) -> Option<Rc<RefCell<Thread>>> {
self.ready_list.pop()
}
@ -51,34 +53,16 @@ impl Scheduler {
/// ## Parameter
///
/// **next_thread** thread to dispatch to the CPU
pub fn switch_to(&self, next_thread: Thread) {
match G_CURRENT_THREAD.write() {
Ok(mut current_thread) => {
let old_thread = current_thread.as_mut().unwrap();
pub fn switch_to(&self, system: &System, next_thread: Rc<RefCell<Thread>>) {
/* if let Some(old_thread) = system.get_g_current_thread() {
old_thread.save_processor_state();
old_thread.save_simulator_state();
old_thread.save_processor_state();
old_thread.save_simulator_state();
if old_thread != &next_thread {
next_thread.restore_processor_state();
next_thread.restore_simulator_state();
current_thread.replace(next_thread);
}
match G_THREAD_TO_BE_DESTROYED.write() {
Ok(mut thread_to_be_destroyed) => {
if thread_to_be_destroyed.is_some() {
drop(thread_to_be_destroyed.take());
}
},
Err(err) => {
panic!("RwLock is poisonned: {}", err);
}
}
},
Err(err) => {
panic!("RwLock is poisonned: {}", err);
if old_thread != &next_thread {
next_thread.restore_processor_state();
next_thread.restore_simulator_state();
system.set_g_current_thread(Option::Some(next_thread));
}
}
} */
}
}

125
src/kernel/synch.rs Normal file
View File

@ -0,0 +1,125 @@
use crate::utility::list::List;
use crate::kernel::thread::Thread;
use crate::simulator::interrupt::InterruptStatus::InterruptOff;
use crate::simulator::machine::Machine;
use std::cell::RefCell;
use std::rc::Rc;
use super::scheduler::Scheduler;
use super::thread_manager::ThreadManager;
pub struct Semaphore<'t> {
counter:i32,
waiting_queue:List<Rc<RefCell<Thread>>>,
thread_manager: Rc<RefCell<ThreadManager<'t>>> // On s'assure que le tm vit plus longtemps que les semaphore avec le lifetime
}
impl<'t> Semaphore<'_> {
pub fn p(&mut self, current_thread: Rc<RefCell<Thread>>, machine: &mut Machine){
let old_status = machine.interrupt.set_status(InterruptOff);
self.counter -= 1;
if self.counter < 0 {
self.waiting_queue.push(Rc::clone(&current_thread));
self.thread_manager.borrow_mut().thread_sleep(current_thread);
}
machine.interrupt.set_status(old_status);
}
pub fn v(&mut self, machine: &mut Machine, scheduler: &mut Scheduler){
let old_status = machine.interrupt.set_status(InterruptOff);
self.counter -= 1;
if self.waiting_queue.peek() != None {
scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
}
machine.interrupt.set_status(old_status);
}
}
pub struct Lock<'t>{
owner: Rc<RefCell<Thread>>,
waiting_queue:List<Rc<RefCell<Thread>>>,
thread_manager: Rc<RefCell<ThreadManager<'t>>>,
free: bool
}
impl<'t> Lock<'_> {
pub fn acquire(&mut self, machine: &mut Machine, current_thread: Rc<RefCell<Thread>>) {
let old_status = machine.interrupt.set_status(InterruptOff);
if self.free {
self.free = false;
self.owner = current_thread;
} else {
self.waiting_queue.push(Rc::clone(&current_thread));
self.thread_manager.borrow_mut().thread_sleep(current_thread);
}
machine.interrupt.set_status(old_status);
}
pub fn release(&mut self, machine: &mut Machine, scheduler: &mut Scheduler, current_thread: Rc<RefCell<Thread>>) {
let old_status = machine.interrupt.set_status(InterruptOff);
if self.is_held_by_current_thread(current_thread) {
if self.waiting_queue.peek() != None {
self.owner = self.waiting_queue.pop().unwrap();
scheduler.ready_to_run(Rc::clone(&self.owner));
} else {
self.free = true;
}
}
machine.interrupt.set_status(old_status);
}
pub fn is_held_by_current_thread(&mut self, current_thread: Rc<RefCell<Thread>>) -> bool {
Rc::ptr_eq(&self.owner, &current_thread)
}
}
pub struct Condition<'t>{
waiting_queue:List<Rc<RefCell<Thread>>>,
thread_manager: Rc<RefCell<ThreadManager<'t>>>,
}
impl<'t> Condition<'_> {
pub fn wait(&mut self, machine: &mut Machine, current_thread: Rc<RefCell<Thread>>) {
let old_status = machine.interrupt.set_status(InterruptOff);
self.waiting_queue.push(Rc::clone(&current_thread));
self.thread_manager.borrow_mut().thread_sleep(current_thread);
machine.interrupt.set_status(old_status);
}
pub fn signal(&mut self, machine: &mut Machine, scheduler: &mut Scheduler) {
let old_status = machine.interrupt.set_status(InterruptOff);
if self.waiting_queue.peek() != None {
scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
}
machine.interrupt.set_status(old_status);
}
pub fn broadcast(&mut self, machine: &mut Machine, scheduler: &mut Scheduler) {
let old_status = machine.interrupt.set_status(InterruptOff);
while self.waiting_queue.peek() != None {
scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
}
machine.interrupt.set_status(old_status);
}
}

View File

@ -1,21 +1,59 @@
use std::{sync::{RwLock, Arc}};
use std::cell::RefCell;
use lazy_static::lazy_static;
use crate::simulator::machine::Machine;
use crate::{kernel::{thread::Thread, scheduler::Scheduler}, utility::list::List, simulator::machine::Machine};
use super::thread_manager::ThreadManager;
extern crate lazy_static;
lazy_static! {
pub static ref G_MACHINE: RwLock<Machine> = RwLock::new(Machine::_init_machine());
pub static ref G_CURRENT_THREAD: RwLock<Option<Thread>> = RwLock::new(Option::None);
pub static ref G_THREAD_TO_BE_DESTROYED: RwLock<Option<Thread>> = RwLock::new(Option::None);
pub static ref G_ALIVE: RwLock<List<Arc<Thread>>> = RwLock::new(List::new());
pub static ref G_SCHEDULER: RwLock<Scheduler> = RwLock::new(Scheduler::new());
/// # System
///
/// This structure represents the state of the threads running on the operating system.
/// It contains references to the following:
///
/// - The simulated machine
/// - The current running thread
/// - The list of active threads
/// - The thread to be destroyed next
/// - The scheduler which acts upon these threads
#[derive(PartialEq)]
pub struct System<'a> {
g_machine: RefCell<Machine>,
thread_manager: ThreadManager<'a>
}
impl<'a> System<'a> {
#[derive(PartialEq)]
/// System constructor
pub fn new(machine: Machine) -> System<'a> {
Self {
g_machine: RefCell::new(machine),
thread_manager: ThreadManager::new()
}
}
/// use thread_manager setter to send it system instance
pub fn freeze(&'a mut self) {
self.thread_manager.system.set(Option::Some(self));
}
// GETTERS
/// Returns the Machine
///
/// Useful to access RAM, devices, ...
pub fn get_g_machine(&self) -> &RefCell<Machine> {
&self.g_machine
}
// Setters
/// Assign a machine to the system
pub fn set_g_machine(&mut self, machine: RefCell<Machine>) {
self.g_machine = machine
}
}
#[derive(PartialEq, Debug)]
pub enum ObjectType {
SemaphoreType,
LockType,
@ -24,3 +62,25 @@ pub enum ObjectType {
ThreadType,
InvalidType
}
#[cfg(test)]
mod tests {
use crate::{System, Machine};
macro_rules! init_system {
() => {{
let m = Machine::init_machine();
init_system!(m)
}};
($a:expr) => {{
System::new($a)
}};
}
#[test]
fn test_init_system() {
init_system!();
}
}

View File

@ -1,80 +1,50 @@
use std::{sync::Arc};
use super::{process::Process, system::ObjectType, thread_manager::SIMULATORSTACKSIZE};
use crate::{simulator::machine::{NUM_INT_REGS, NUM_FP_REGS, STACK_REG}};
use super::{process::Process, mgerror::ErrorCode, system::{ObjectType, G_ALIVE, G_SCHEDULER}, ucontext::UContextT};
use crate::{simulator::machine::{NUM_INT_REGS, NUM_FP_REGS, STACK_REG}, kernel::system::{G_MACHINE, G_THREAD_TO_BE_DESTROYED}};
const SIMULATORSTACKSIZE: usize = 32 * 1024;
const STACK_FENCEPOST: u32 = 0xdeadbeef;
#[derive(PartialEq)]
struct ThreadContext {
#[derive(PartialEq, Debug)]
pub struct ThreadContext {
pub int_registers: [i64; NUM_INT_REGS],
pub float_registers: [i64; NUM_FP_REGS],
pub float_registers: [f32; NUM_FP_REGS],
pc: i64,
}
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
pub struct Thread {
name: String,
process: Option<Process>,
pub process: Option<Process>,
// simulation_context: UContextT,
thread_context: ThreadContext,
stack_pointer: i32,
pub thread_context: ThreadContext,
pub stack_pointer: i32,
object_type: ObjectType
}
impl Thread {
pub fn new(name: String) -> Self {
/// Thread constructor
pub fn new(name: &str) -> Self {
Self {
name,
name: String::from(name),
process: None,
// simulation_context: UContextT::new(),
thread_context: ThreadContext {
int_registers: [0; NUM_INT_REGS],
float_registers: [0; NUM_FP_REGS],
float_registers: [0f32; NUM_FP_REGS],
pc: 0
},
stack_pointer: 0,
object_type: ObjectType::ThreadType
object_type: ObjectType::ThreadType,
}
}
/// Start a thread, attaching it to a process
pub fn start(mut self, owner: Process, func: i64, arg: i64) -> Result<(), ErrorCode> {
self.process = Option::Some(owner);
let ptr = 0; // todo addrspace
self.init_thread_context(func, ptr, arg);
let base_stack_addr: [i8; SIMULATORSTACKSIZE] = [0; SIMULATORSTACKSIZE]; // todo AllocBoundedArray
self.init_simulator_context(base_stack_addr);
self.process.as_mut().unwrap().num_thread += 1;
match G_ALIVE.write() {
Ok(mut alive) => {
let this = Arc::new(self);
alive.push(Arc::clone(&this));
match G_SCHEDULER.write() {
Ok(mut scheduler) => {
scheduler.ready_to_run(Arc::clone(&this));
},
Err(err) => {
panic!("RwLock poisonned, {}", err);
}
}
},
Err(err) => {
panic!("RwLock poisonned, {}", err);
}
}
Result::Ok(())
}
fn init_thread_context(&mut self, initial_pc_reg: i64, initial_sp: i64, arg: i64) {
pub fn init_thread_context(&mut self, initial_pc_reg: i64, initial_sp: i64, arg: i64) {
self.thread_context.pc = initial_pc_reg;
self.thread_context.int_registers[10] = arg;
self.thread_context.int_registers[STACK_REG] = initial_sp;
}
fn init_simulator_context(&self, base_stack_addr: [i8; SIMULATORSTACKSIZE]) {
pub fn init_simulator_context(&self, base_stack_addr: [i8; SIMULATORSTACKSIZE]) {
// let res = self.simulation_context.get_context();
// if res != 0 {
// panic!("getcontext returns non-zero value {}", res);
@ -89,83 +59,22 @@ impl Thread {
// self.simulation_context.stackBottom[0] = STACK_FENCEPOST;
}
/// Wait for another thread to finish its execution
pub fn join(&self, id_thread: Arc<Thread>) {
match G_ALIVE.write() {
Ok(alive) => {
while alive.contains(&Arc::clone(&id_thread)) {
self.t_yield();
}
},
Err(err) => {
panic!("RwLock poisonned, {}", err)
}
}
}
/// Relinquish the CPU if any other thread is runnable.
///
/// Cannot use yield as a function name -> reserved name in rust
pub fn t_yield(&self) {
todo!();
}
/// Put the thread to sleep and relinquish the processor
pub fn sleep(&self) {
todo!();
}
/// Finish the execution of the thread and prepare its deallocation
pub fn finish(mut self) {
match G_MACHINE.write() {
Ok(mut machine) => {
let old_status = machine.interrupt.set_status(crate::simulator::interrupt::InterruptStatus::InterruptOff);
match G_ALIVE.write() {
Ok(alive) => {
// todo alive.remove(T) à implémenter dans List
},
Err(err) => {
panic!("RwLock is poisoned: {}", err);
}
}
match G_THREAD_TO_BE_DESTROYED.write() {
Ok(mut thread_to_be_destroyed) => {
thread_to_be_destroyed.replace(self);
},
Err(err) => {
panic!("RwLock is poisoned: {}", err);
}
}
// self.sleep();
machine.interrupt.set_status(old_status);
},
Err(err) => {
panic!("RwLock is poisoned: {}", err);
}
}
todo!();
}
/// Check if a thread has overflowed its stack
///
/// This assertion doesn't catch all stack overflow conditions and your program may still crash because of an overflow.
///
pub fn check_overflow(&self) {
todo!();
}
pub fn save_processor_state(&self) {
todo!();
}
pub fn restore_processor_state(&self) {
todo!();
// if self.simulator_context.stackBottom != STACK_FENCEPOST {
// panic!("thread {} has overflowed", self.get_name())
// }
}
pub fn save_simulator_state(&self) {
todo!();
// todo!(); // simulator state will maybe be removed so panic call is remove. See ucontext.rs
}
pub fn restore_simulator_state(&self) {
todo!();
// todo!(); // simulator state will maybe be removed so panic call is remove. See ucontext.rs
}
pub fn get_name(&self) -> String {
@ -186,3 +95,54 @@ impl Drop for Thread {
fn start_thread_execution() {
}
#[cfg(test)]
mod test {
use super::{Thread, ThreadContext, NUM_INT_REGS, NUM_FP_REGS, ObjectType};
const DEFAULT_THREAD_NAME: &str = "test_thread";
/// Polymorphic macro to get thread without passing a name by default
macro_rules! get_new_thread {
() => { Thread::new(DEFAULT_THREAD_NAME) };
($a:literal) => {
Thread::new(&$a.to_string())
};
}
/// This macro allows for getting a Thread for which we've ensured proper initial state
/// in case a commit further down the line changes the initial state of threads generated
/// from Thread::new
macro_rules! expected_initial_state {
() => { expected_initial_state!(DEFAULT_THREAD_NAME) };
($a:expr) => { {
let mut x = Thread::new($a);
x.name = $a.to_string();
x.process = Option::None;
x.thread_context = ThreadContext {
int_registers: [0; NUM_INT_REGS],
float_registers: [0f32; NUM_FP_REGS],
pc: 0
};
x.stack_pointer = 0;
x.object_type = ObjectType::ThreadType;
x }
};
}
#[test]
fn test_macro() {
let t = get_new_thread!("hello");
assert_eq!(t.get_name(), "hello");
let t = get_new_thread!(1);
assert_eq!(t.get_name(), "1");
}
#[test]
fn check_init() {
let t = get_new_thread!();
let expected_state = expected_initial_state!();
assert_eq!(t, expected_state)
}
}

View File

@ -0,0 +1,138 @@
use std::{rc::Rc, cell::{Cell, RefCell, RefMut, Ref}};
use crate::{utility::list::List, simulator::machine::{NUM_INT_REGS, NUM_FP_REGS}};
use super::{scheduler::Scheduler, thread::Thread, system::System, mgerror::ErrorCode, process::Process};
pub const SIMULATORSTACKSIZE: usize = 32 * 1024;
#[derive(PartialEq)]
pub struct ThreadManager<'a> {
pub g_current_thread: Option<Thread>,
pub g_thread_to_be_destroyed: Option<Thread>,
pub g_alive: List<Rc<RefCell<Thread>>>,
pub g_scheduler: Scheduler,
pub system: Cell<Option<&'a System<'a>>>
}
impl<'a> ThreadManager<'a> {
pub fn new() -> Self {
Self {
g_current_thread: Option::None,
g_thread_to_be_destroyed: Option::None,
g_alive: List::new(),
g_scheduler: Scheduler::new(),
system: Cell::new(None)
}
}
/// Start a thread, attaching it to a process
pub fn start_thread(&mut self, thread: Rc<RefCell<Thread>>, owner: Process, func_pc: i64, argument: i64) -> Result<(), ErrorCode> {
let mut thread_m = thread.borrow_mut();
thread_m.process = Option::Some(owner);
let ptr = 0; // todo addrspace
thread_m.init_thread_context(func_pc, ptr, argument);
let base_stack_addr: [i8; SIMULATORSTACKSIZE] = [0; SIMULATORSTACKSIZE]; // todo AllocBoundedArray
thread_m.init_simulator_context(base_stack_addr);
thread_m.process.as_mut().unwrap().num_thread += 1;
self.get_g_alive().push(Rc::clone(&thread));
self.g_scheduler().ready_to_run(Rc::clone(&thread));
Result::Ok(())
}
/// Wait for another thread to finish its execution
pub fn thread_join(&mut self, id_thread: Rc<RefCell<Thread>>) {
while self.get_g_alive().contains(&Rc::clone(&id_thread)) {
self.thread_yield(Rc::clone(&id_thread));
}
}
/// Relinquish the CPU if any other thread is runnable.
///
/// Cannot use yield as a function name -> reserved name in rust
pub fn thread_yield(&mut self, thread: Rc<RefCell<Thread>>) {
if let Some(system) = self.system.get() {
let mut machine = system.get_g_machine().borrow_mut();
let old_status = machine.interrupt.set_status(crate::simulator::interrupt::InterruptStatus::InterruptOff);
let next_thread = self.g_scheduler().find_next_to_run();
if let Some(next_thread) = next_thread {
let scheduler = self.g_scheduler();
scheduler.ready_to_run(thread);
scheduler.switch_to(system, next_thread);
}
machine.interrupt.set_status(old_status);
}
}
/// Put the thread to sleep and relinquish the processor
pub fn thread_sleep(&mut self, thread: Rc<RefCell<Thread>>) {
todo!();
}
/// Finish the execution of the thread and prepare its deallocation
pub fn thread_finish(&self, thread: Rc<RefCell<Thread>>) {
todo!();
}
pub fn thread_save_processor_state(&mut self, thread: Rc<RefCell<Thread>>) {
if let Some(system) = self.system.get() {
let mut t: RefMut<_> = thread.borrow_mut();
for i in 0..NUM_INT_REGS {
t.thread_context.int_registers[i] = system.get_g_machine().borrow().read_int_register(i);
}
for i in 0..NUM_FP_REGS {
t.thread_context.float_registers[i] = system.get_g_machine().borrow().read_fp_register(i);
}
} else {
unreachable!("System is None")
}
}
pub fn thread_restore_processor_state(&self, thread: Rc<RefCell<Thread>>) {
if let Some(system) = self.system.get() {
let t: Ref<_> = thread.borrow();
for i in 0..NUM_INT_REGS {
let machine = system.get_g_machine();
let mut machine = machine.borrow_mut();
machine.write_int_register(i, t.thread_context.int_registers[i]);
}
} else {
unreachable!("System is None")
}
}
/// Currently running thread
pub fn get_g_current_thread(&mut self) -> &mut Option<Thread> {
&mut self.g_current_thread
}
/// Thread to be destroyed by [...]
///
/// TODO: Finish the comment with the relevant value
pub fn get_g_thread_to_be_destroyed(&mut self) -> &mut Option<Thread> {
&mut self.g_thread_to_be_destroyed
}
/// List of alive threads
pub fn get_g_alive(&mut self) -> &mut List<Rc<RefCell<Thread>>> {
&mut self.g_alive
}
/// Current scheduler
pub fn g_scheduler(&mut self) -> &mut Scheduler {
&mut self.g_scheduler
}
/// Set currently running thread
pub fn set_g_current_thread(&mut self, thread: Option<Thread>) {
self.g_current_thread = thread
}
/// Set thread to be destroyed next
pub fn set_g_thread_to_be_destroyed(&mut self, thread: Option<Thread>) {
self.g_thread_to_be_destroyed = thread
}
}

View File

@ -1,4 +1,4 @@
#[cfg(not(target_os = "windows"))]
use std::mem::MaybeUninit;
/// Safe wrapper for ucontext_t struct of linux-gnu libc
@ -11,19 +11,20 @@ use std::mem::MaybeUninit;
#[derive(PartialEq)]
pub struct UContextT {
#[cfg(not(target_os = "windows"))] // struct non disponible sur la libc sur windows
pub buf: lib::ucontext_t,
pub stackBottom: Vec<i8>
pub buf: libc::ucontext_t,
pub stack_bottom: Vec<i8>
}
#[cfg(not(target_os = "windows"))]
#[allow(unused)] // Temporary as we currently doesn't use this structure (this structure may disapear in a near future)
impl UContextT {
pub fn new() -> Self {
let mut context = MaybeUninit::<ucontext_t>::uninit();
unsafe { lib::getcontext(context.as_mut_ptr()) };
let mut context = MaybeUninit::<libc::ucontext_t>::uninit();
unsafe { libc::getcontext(context.as_mut_ptr()) };
Self {
buf: unsafe { context.assume_init() },
stackBottom: Vec::default(),
stack_bottom: Vec::default(),
}
}
@ -32,7 +33,7 @@ impl UContextT {
/// Use `man getcontext` for more informations
pub fn get_context(&mut self) -> i32 {
unsafe {
lib::getcontext(&mut self.buf)
libc::getcontext(&mut self.buf)
}
}
@ -41,24 +42,25 @@ impl UContextT {
/// Use `man setcontext` for more informations
pub fn set_context(&mut self) -> i32 {
unsafe {
lib::setcontext(&self.buf)
libc::setcontext(&self.buf)
}
}
pub fn make_context(&mut self, func: extern "C" fn(), args: i32) {
unsafe {
lib::makecontext(&mut self.buf, func, args)
libc::makecontext(&mut self.buf, func, args)
}
}
}
#[cfg(target_os = "windows")]
#[allow(unused)]
impl UContextT {
pub fn new() -> Self {
Self {
stackBottom: Vec::default()
stack_bottom: Vec::default()
}
}

View File

@ -1,16 +1,23 @@
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
//! Crate burritos ((BurritOS Using Rust Really Improves The Operating System)
//!
//! Burritos is an educational operating system written in Rust
//! running on RISC-V emulator.
/// Contain hardware simulated part of the machine
mod simulator;
mod kernel;
/// module containing useful tools which can be use in most part of the OS to ease the development of the OS
pub mod utility;
use kernel::system::System;
use simulator::machine::Machine;
fn main() {
let mut m = Machine::_init_machine();
m.main_memory[4] = 43;
m.main_memory[5] = 150;
let a : u8 = 128;
let b : i8 = a as i8;
let c : u8 = b as u8;
println!("aaa {c}");
println!("read_memory : {}", Machine::read_memory(&mut m, 2, 4));
let machine = Machine::init_machine();
let mut system = System::new(machine);
system.freeze();
}

View File

@ -1,5 +1,5 @@
#[derive(PartialEq)]
pub struct Interrupt {
level: InterruptStatus
}
@ -12,10 +12,10 @@ impl Interrupt {
}
}
pub fn set_status(&mut self, newStatus: InterruptStatus) -> InterruptStatus {
pub fn set_status(&mut self, new_status: InterruptStatus) -> InterruptStatus {
let old = self.level;
self.level = newStatus;
if newStatus == InterruptStatus::InterruptOn && old == InterruptStatus::InterruptOff {
self.level = new_status;
if new_status == InterruptStatus::InterruptOn && old == InterruptStatus::InterruptOff {
self.one_tick(1);
}
old

34
src/simulator/loader.rs Normal file
View File

@ -0,0 +1,34 @@
use crate::Machine;
use std::fs;
use std::io;
use std::io::BufRead;
/// Load a file into a new machine
///
/// `panic!` when size is not 1, 2, 4 or 8
/// `panic!` when the text does not represents instructions in hexadecimal
///
/// ### Parameters
///
/// - **path** the path of the file to load
/// - **size** the number of bytes to write (1, 2, 4 or 8)
pub fn _load(path : &str, instruction_size: i32) -> Machine {
let file = fs::File::open(path).expect("Wrong filename");
let reader = io::BufReader::new(file);
let mut machine = Machine::init_machine();
for (i,line) in reader.lines().enumerate() {
let res = u64::from_str_radix(&line.unwrap(), 16);
match res {
Ok(value) => {
Machine::write_memory(&mut machine, instruction_size, i*instruction_size as usize, value);
},
_ => panic!()
}
}
println!("{:x}", Machine::read_memory(& mut machine, 4, 0));
machine
}

View File

@ -1,16 +1,41 @@
use std::{ops::{Add, Sub}, io::Write};
use crate::simulator::print;
use super::{decode::{Instruction, decode}, interrupt::Interrupt};
use super::global::*;
use std::fs::File;
/*
* Decommenter la variant si il est utilisé quelque part
*/
pub enum ExceptionType {
NO_EXCEPTION,//Everything ok!
//SYSCALL_EXCEPTION,//A program executed a system call.
PAGEFAULT_EXCEPTION,//Page fault exception
READONLY_EXCEPTION,//Write attempted to a page marked "read-only" */
//BUSERROR_EXCEPTION,
/* translation resulted
in an invalid physical
address (mis-aligned or
out-of-bounds) */
ADDRESSERROR_EXCEPTION, /* Reference that was
not mapped in the address
space */
//OVERFLOW_EXCEPTION, //Integer overflow in add or sub.
//ILLEGALINSTR_EXCEPTION, //Unimplemented or reserved instr.
//NUM_EXCEPTION_TYPES
}
pub const STACK_REG: usize = 2;
pub const NUM_INT_REGS: usize = 32;
pub const NUM_FP_REGS: usize = 32;
/// doit disparaitre
const MEM_SIZE : usize = 4096;
const MEM_SIZE : usize = 0x500000;
pub trait RegisterNum: Add<Output=Self> + Sub<Output=Self> + PartialEq + Copy {}
@ -19,7 +44,7 @@ impl RegisterNum for i64 {}
impl RegisterNum for f32 {}
#[derive(PartialEq)]
pub struct Register<U: RegisterNum> {
register: [U; 32]
}
@ -45,7 +70,7 @@ impl Register<i64> {
self.register[position] = value;
} else {
// Panic ou rien ? (dans le doute pour le moment panic)
unreachable!("You can't write to zero register")
// unreachable!("You can't write to zero register")
}
}
@ -65,14 +90,16 @@ impl Register<f32> {
}
#[derive(PartialEq)]
pub struct Machine {
pub pc : u64,
pub sp: usize,
pub int_reg : Register<i64>,
pub fp_reg : Register<f32>,
pub main_memory : [u8 ; MEM_SIZE],
pub main_memory : Vec<u8>,
pub shiftmask : [u64 ; 64],
pub interrupt: Interrupt,
pub registers_trace : String, // for tests
pub interrupt: Interrupt
// futur taille à calculer int memSize = g_cfg->NumPhysPages * g_cfg->PageSize;
//creer une struct cfg(configuration) qui s'initialise avec valeur dans un fichier cfg
}
@ -80,7 +107,7 @@ pub struct Machine {
impl Machine {
pub fn _init_machine() -> Machine {
pub fn init_machine() -> Machine {
let mut shiftmask : [u64 ; 64] = [0 ; 64];
let mut value : u64 = 0xffffffff;
@ -90,16 +117,19 @@ impl Machine {
value >>= 1;
}
Machine {
let mut ret = Machine {
pc : 0,
sp: 0,
int_reg : Register::<i64>::init(),
fp_reg : Register::<f32>::init(),
main_memory : [0 ; MEM_SIZE],
main_memory : vec![0_u8; MEM_SIZE],
shiftmask,
interrupt: Interrupt::new()
}
interrupt: Interrupt::new(),
registers_trace : String::from("")
};
ret.int_reg.set_reg(10, -1);
ret
}
/// Read from main memory of the machine
@ -150,9 +180,43 @@ impl Machine {
/// ### Parameters
///
/// - **machine** contains the memory
pub fn extract_memory(machine: &mut Machine){
let mut file = File::create("burritos_memory.txt").unwrap();
file.write(&machine.main_memory);
pub fn _extract_memory(machine: &mut Machine){
let file_path = "burritos_memory.txt";
let write_to_file = |path| -> std::io::Result<File> {
let mut file = File::create(path)?;
file.write_all(&machine.main_memory)?;
Ok(file)
};
match write_to_file(file_path) {
Err(e) => eprintln!("Failed to write memory to file: {}", e),
Ok(_) => println!("Memory extracted to {}", file_path)
};
}
pub fn print_machine_status(machine: &mut Machine) {
println!("######### Machine status #########");
for i in (0..32).step_by(3) {
print!(">{0: <4} : {1:<16x} ", print::REG_X[i], machine.int_reg.get_reg(i));
print!(">{0: <4} : {1:<16x} ", print::REG_X[i+1], machine.int_reg.get_reg(i+1));
if i+2 < 32 {
print!(">{0: <4} : {1:<16x} ", print::REG_X[i+2], machine.int_reg.get_reg(i+2));
}
println!();
}
println!("________________SP________________");
let sp_index = machine.int_reg.get_reg(2);
for i in 0..5 {
println!("SP+{:<2} : {:16x}", i*8, Self::read_memory(machine, 8, (sp_index + i*8) as usize));
}
println!("##################################");
}
pub fn string_registers(machine: &mut Machine) -> String {
let mut s = String::from("");
for i in 0..32 {
s.push_str(format!("{} ", machine.int_reg.get_reg(i)).as_str());
}
s
}
/// Execute the instructions table of a machine putted in param
@ -160,11 +224,9 @@ impl Machine {
/// ### Parameters
///
/// - **machine** which contains a table of instructions
pub fn run(machine : Machine){
let mut m = machine;
loop{
Machine::one_instruction(&mut m);
}
pub fn run(machine : &mut Machine){
while Machine::one_instruction(machine) == 0 {}
println!("trace : \n{}", machine.registers_trace);
}
/// execute the current instruction
@ -172,7 +234,7 @@ impl Machine {
/// ### Parameters
///
/// - **machine** which contains a table of instructions and a pc to the actual instruction
pub fn one_instruction(machine :&mut Machine) {
pub fn one_instruction(machine :&mut Machine) -> i32 {
let unsigned_reg1 : u64;
let unsigned_reg2 : u64;
@ -187,32 +249,37 @@ impl Machine {
uint64_t value;*/
if machine.main_memory.len() <= machine.pc as usize {
println!("ERROR : number max of instructions rushed");
return ;
panic!("ERROR : number max of instructions rushed");
}
let mut val: [u8; 8] = [0; 8];
for i in 0..8 {
val[i] = machine.main_memory[machine.pc as usize + i];
let mut val: [u8; 4] = [0; 4];
for (i, mut _item) in val.iter_mut().enumerate() {
_item = &mut machine.main_memory[machine.pc as usize + i];
}
let val = u64::from_le_bytes(val);
let val = u32::from_be_bytes(val) as u64;
let inst : Instruction = decode(val);
Self::print_machine_status(machine);
println!("executing instruction : {:016x} at pc {:x}", val, machine.pc);
println!("{}", print::print(decode(val), machine.pc as i32));
let trace = Self::string_registers(machine);
machine.registers_trace.push_str(format!("{}\n", trace).as_str());
machine.pc += 4;
match inst.opcode {
RISCV_LUI => {
machine.int_reg.set_reg(inst.rd as usize, inst.imm31_12 as i64);
},
RISCV_AUIPC => {
machine.int_reg.set_reg(inst.rd as usize,machine.pc as i64 - 8 + inst.imm31_12 as i64);
machine.int_reg.set_reg(inst.rd as usize,machine.pc as i64 - 4 + inst.imm31_12 as i64);
},
RISCV_JAL => {
machine.int_reg.set_reg(inst.rd as usize, machine.pc as i64);
machine.pc += inst.imm21_1_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm21_1_signed as i64 - 4) as u64;
},
RISCV_JALR => {
let tmp = machine.pc;
machine.pc = (machine.int_reg.get_reg(inst.rs1 as usize) as u64 + inst.imm12_I_signed as u64) & 0xfffffffe;
machine.pc = (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as u64 & 0xfffffffe;
machine.int_reg.set_reg(inst.rd as usize, tmp as i64);
},
@ -222,32 +289,32 @@ impl Machine {
match inst.funct3 {
RISCV_BR_BEQ => {
if machine.int_reg.get_reg(inst.rs1 as usize) == machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
RISCV_BR_BNE => {
if machine.int_reg.get_reg(inst.rs1 as usize) != machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
RISCV_BR_BLT => {
if machine.int_reg.get_reg(inst.rs1 as usize) < machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
RISCV_BR_BGE => {
if machine.int_reg.get_reg(inst.rs1 as usize) >= machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
RISCV_BR_BLTU => {
if machine.int_reg.get_reg(inst.rs1 as usize) < machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
RISCV_BR_BGEU => {
if machine.int_reg.get_reg(inst.rs1 as usize) >= machine.int_reg.get_reg(inst.rs2 as usize) {
machine.pc += inst.imm13_signed as u64 - 8;
machine.pc = (machine.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
}
},
_ => {
@ -273,7 +340,7 @@ impl Machine {
machine.int_reg.set_reg(inst.rd as usize, tmp);
},
RISCV_LD_LD => {
let tmp = Self::read_memory(machine, 1, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64;
let tmp = Self::read_memory(machine, 8, (machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64) as usize) as i64;
machine.int_reg.set_reg(inst.rd as usize, tmp);
},
_ => {
@ -309,7 +376,7 @@ impl Machine {
machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) + inst.imm12_I_signed as i64);
},
RISCV_OPI_SLTI => {
machine.int_reg.set_reg(inst.rd as usize, if machine.int_reg.get_reg(inst.rs1 as usize) < inst.imm12_I_signed as i64 { 1 } else { 0 } );
machine.int_reg.set_reg(inst.rd as usize, (machine.int_reg.get_reg(inst.rs1 as usize) < inst.imm12_I_signed as i64) as i64);
},
RISCV_OPI_XORI => {
machine.int_reg.set_reg(inst.rd as usize, machine.int_reg.get_reg(inst.rs1 as usize) ^ inst.imm12_I_signed as i64);
@ -429,12 +496,11 @@ impl Machine {
machine.int_reg.set_reg(inst.rd as usize, result);
},
RISCV_OPIW_SRW => {
let result;
if inst.funct7 == RISCV_OPIW_SRW_SRLIW {
result = (local_data >> inst.shamt) & machine.shiftmask[32 + inst.shamt as usize] as i64;
let result = if inst.funct7 == RISCV_OPIW_SRW_SRLIW {
(local_data >> inst.shamt) & machine.shiftmask[32 + inst.shamt as usize] as i64
} else { // SRAIW
result = local_data >> inst.shamt;
}
local_data >> inst.shamt
};
machine.int_reg.set_reg(inst.rd as usize, result);
},
_ => {
@ -591,13 +657,13 @@ impl Machine {
RISCV_FP_FCMP => {
match inst.funct3 {
RISCV_FP_FCMP_FEQ => {
machine.int_reg.set_reg(inst.rd as usize, if machine.fp_reg.get_reg(inst.rs1 as usize) == machine.fp_reg.get_reg(inst.rs2 as usize) {1} else {0});
machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) == machine.fp_reg.get_reg(inst.rs2 as usize)) as i64);
},
RISCV_FP_FCMP_FLT => {
machine.int_reg.set_reg(inst.rd as usize, if machine.fp_reg.get_reg(inst.rs1 as usize) < machine.fp_reg.get_reg(inst.rs2 as usize) {1} else {0});
machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) < machine.fp_reg.get_reg(inst.rs2 as usize)) as i64);
},
RISCV_FP_FCMP_FLE => {
machine.int_reg.set_reg(inst.rd as usize, if machine.fp_reg.get_reg(inst.rs1 as usize) <= machine.fp_reg.get_reg(inst.rs2 as usize) {1} else {0});
machine.int_reg.set_reg(inst.rd as usize, (machine.fp_reg.get_reg(inst.rs1 as usize) <= machine.fp_reg.get_reg(inst.rs2 as usize)) as i64);
},
_ => {
panic!("this instruction ({}) doesn't exists", inst.value);
@ -609,21 +675,62 @@ impl Machine {
}
}
}
_ => { panic!("{} opcode non géré", inst.opcode)},
RISCV_SYSTEM => {
// temporary return value to stop the loop of run
// before we can use system call
return 1;
}
_ => { panic!("{:x} opcode non géré pc : {:x}", inst.opcode, machine.pc)},
}
machine.pc += 8;
0
}
/// print memory FOR DEBUG
///
/// "@"adresse [16 bytes]
pub fn _print_memory(machine : &mut Machine, from: usize, to: usize) {
for i in from..to {
if i%16 == 0 {
print!("\n@{:04x} ", i);
}
print!("{:02x}", machine.main_memory[i]);
}
println!();
}
pub fn read_int_register(&self, index: usize) -> i64 {
self.int_reg.get_reg(index)
}
pub fn read_fp_register(&self, index: usize) -> f32 {
self.fp_reg.get_reg(index)
}
pub fn write_int_register(&mut self, index: usize, value: i64) {
self.int_reg.set_reg(index, value);
}
pub fn write_fp_register(&mut self, index: usize, value: f32) {
self.fp_reg.set_reg(index, value);
}
}
#[cfg(test)]
mod test {
use crate::simulator::machine::Machine;
mod test {
use std::fs;
use crate::simulator::{machine::Machine, mem_cmp};
#[test]
fn test_init_machine() {
let _ = Machine::init_machine();
}
#[test]
fn test_read_memory() {
let mut m = Machine::_init_machine();
let mut m = Machine::init_machine();
m.main_memory[4] = 43;
m.main_memory[5] = 150;
assert_eq!((43 << 8) + 150, Machine::read_memory(&mut m, 2, 4));
@ -631,9 +738,126 @@ impl Machine {
#[test]
fn test_write_memory() {
let mut m = Machine::_init_machine();
let mut m = Machine::init_machine();
Machine::write_memory(&mut m, 2, 6, (43 << 8) + 150);
assert_eq!(43, m.main_memory[6]);
assert_eq!(150, m.main_memory[7]);
Machine::write_memory(&mut m, 4, 8, (52 << 24) + (20 << 16) + (43 << 8) + 150);
assert_eq!(52, m.main_memory[8]);
assert_eq!(20, m.main_memory[9]);
assert_eq!(43, m.main_memory[10]);
assert_eq!(150, m.main_memory[11]);
}
#[test]
fn test_comp() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryComp.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryCompEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryCompTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_div() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryDiv.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryDivEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryDivTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_if() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryIf.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryIfEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryIfTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_jump() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryJump.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryJumpEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryJumpTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_mul() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryMul.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryMulEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryMulTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_ret() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memoryRet.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memoryRetEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memoryRetTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_sub() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memorySub.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memorySubEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memorySubTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
#[test]
fn test_switch() {
let mut m = Machine::init_machine();
let memory_before = mem_cmp::MemChecker::from("test/machine/memorySwitch.txt").unwrap();
let memory_after = mem_cmp::MemChecker::from("test/machine/memorySwitchEnd.txt").unwrap();
mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
Machine::run(&mut m);
let expected_trace = fs::read_to_string("test/machine/memorySwitchTrace.txt").unwrap();
assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
assert!(expected_trace.contains(m.registers_trace.as_str()));
}
}

View File

@ -1,12 +1,9 @@
use std::fs;
use std::io;
use std::io::BufRead;
use std::{fs, io::{BufRead, BufReader, Lines, Error}};
use crate::Machine;
const MEM_SIZE : usize = 4096;
/* TRUCS MANQUANTS
* Verifier qu'il y a un nombre pair de caractere hexa dans la ligne correspondante d'une section du fichier source
* Sinon on ne peut pas automatiquement remplir chaque octect car 2 hexa = 1 octet
@ -30,14 +27,14 @@ const MEM_SIZE : usize = 4096;
//content est une suite hexadécimale
//Section dans le fichier, champ String car informations proviennent d'un fichier txt
struct SectionFormat{
pub struct SectionFormat{
addr: String,
len: String,
content: String,
}
//Section dans le programme
struct Section{
pub struct Section{
addr: usize, // adresse dans la mémoire
len: usize, // nombre d'octets de la donnée à addr
content: Vec<u8>, // la donnée en question
@ -55,7 +52,6 @@ impl Section{
let len: usize = string_hex_to_usize(&section.len);
let mut tmp_a: char = ' ';
let mut tmp_b: char = ' ';
for (i, c) in section.content.chars().enumerate(){
@ -63,18 +59,17 @@ impl Section{
tmp_a = c;
}
else {
tmp_b = c;
content.push(two_hex_to_u8(tmp_a,tmp_b));
content.push(two_hex_to_u8(tmp_a,c));
}
}
Section{addr:addr, len:len, content:content}
Section{addr, len, content}
}
fn print_Section(s: &Section){
println!("ADDR :: {}", s.addr);
println!("LEN :: {}", s.len);
fn print_section(s: &Section){
println!("ADDR :: {:x}", s.addr);
println!("LEN :: {:x}", s.len);
println!("CONTENT :: {:?}", s.content);
}
}
@ -82,78 +77,111 @@ impl Section{
/*
* Representation de l'etat de la mémoire (apres execution.... a confirmer), sous forme de sections
*/
struct Mem_Checker{
pub struct MemChecker{
pc: usize,
sp: usize,
sections: Vec<Section>,
}
impl Mem_Checker{
impl MemChecker{
fn from(path: &String) -> Mem_Checker {
///Translate lines of a file in e Vector of String
///We need this method to parse the memory we received
///
/// ### Parameters
///
/// - **Lines** The file to parse
///
/// ### Return
/// - A vector of String where each line of the file os an element of the vector
fn vect_from_lines(lines: &mut Lines<BufReader<fs::File>>, pc: &mut usize, sp: &mut usize) -> Vec<String>{
let mut vector = Vec::new();
for (_,line) in lines.enumerate() {
vector.push(line.unwrap());
}
let size = vector.len();
*pc = string_hex_to_usize(vector.get(size - 2).expect("0"));
*sp = string_hex_to_usize(vector.get(size - 1).expect("0"));
vector
}
let file = fs::File::open("test_file_section.txt").expect("Wrong filename");
let reader = io::BufReader::new(file);
/// Fill a mem checker from a file (here the mock memory)
/// Extract the values of pc, sp and sections
///
/// ### Parameter
/// -**path** addr to the file
///
/// ### Return
/// Mem-checker filled
pub fn from(path: &str) -> Result<MemChecker, Error> {
let file = fs::File::open(path)?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
let mut pc: usize = 0;
let mut sp: usize = 0;
let mut sections: Vec<Section> = Vec::new();
let vector = MemChecker::vect_from_lines(&mut lines, &mut pc, &mut sp);
let mut sections: Vec<Section> = Vec::new();
let mut tmp_addr_str: String = String::new();
let mut tmp_len_str: String = String::new();
for (i,line) in reader.lines().enumerate() {
let default = String::new();
for i in 0..vector.len()-2 {
let current_line = vector.get(i).unwrap_or(&default);
let current_line = line.unwrap();
if i == current_line.len()-2 {
//Lecture de PC
pc = string_hex_to_usize(&current_line);
}
else if i == current_line.len()-1 {
//Lecture SP
sp = string_hex_to_usize(&current_line);
//Lecture des sections
if i % 2 == 0 {
//lecture ligne ADDR LEN
let next_word_index = current_line.find(' ').unwrap();
tmp_addr_str = String::from(&current_line[0..next_word_index]);
tmp_len_str = String::from(&current_line[next_word_index+1..]);
}
else {
//Lecture des sections
if current_line.contains(' ') {
//lecture ligne ADDR LEN
let next_word_index = current_line.find(' ').unwrap();
tmp_addr_str = String::from(&current_line[0..next_word_index]);
tmp_len_str = String::from(&current_line[next_word_index+1..]);
}
else {
//lecture ligne CONTENT
let section_f = SectionFormat{
addr: tmp_addr_str.clone(),
len: tmp_len_str.clone(),
content: current_line,
};
sections.push(Section::from(&section_f));
}
//lecture ligne CONTENT
let section_f = SectionFormat{
addr: tmp_addr_str.clone(),
len: tmp_len_str.clone(),
content: current_line.clone().replace(' ', ""),
};
sections.push(Section::from(&section_f));
}
}
Mem_Checker{pc:pc, sp:sp, sections:sections}
Ok(MemChecker{pc, sp, sections})
}
fn print_Mem_Checker(m_c: &Mem_Checker){
println!("PC :: {}", m_c.pc);
println!("SP :: {}", m_c.sp);
/// Print the content of a Mem_Checker
///
/// ### Parameter
///
/// - **m_c** Contains the data we want to print
pub fn print_mem_checker(m_c: &MemChecker){
println!("PC :: {:x}", m_c.pc);
println!("SP :: {:x}", m_c.sp);
for(i,s) in m_c.sections.iter().enumerate() {
println!("\nSection {}\n", i);
Section::print_Section(&s);
Section::print_section(s);
}
}
fn fill_memory_from_Mem_Checker(m_c: &Mem_Checker, machine: &mut Machine){
/// Fill a machine's memory from a Mem Chacker
///
/// ### Parameters
///
/// - **m_c** contains the data
/// - **machine** contains the memry to fill
pub fn fill_memory_from_mem_checker(m_c: &MemChecker, machine: &mut Machine){
machine.sp = m_c.sp;
machine.int_reg.set_reg(2, m_c.sp as i64);
machine.pc = m_c.pc as u64;
@ -169,9 +197,9 @@ impl Mem_Checker{
/*
* FOR DEBUG
*/
fn compare_print_m_c_machine(m_c: &Mem_Checker, machine: &mut Machine){
fn compare_print_m_c_machine(m_c: &MemChecker, machine: &mut Machine){
Mem_Checker::print_Mem_Checker(m_c);
MemChecker::print_mem_checker(m_c);
for section in m_c.sections.iter() {
@ -187,12 +215,27 @@ impl Mem_Checker{
}
/// Compare sections of a memChecker and a machine memory
///
/// ### Parameters
///
/// - **m_c** contains section of the memory checker
/// - **machine** contains the main memory
pub fn compare_machine_memory(m_c: &MemChecker, machine: &Machine) -> bool {
m_c.sections.iter().map(|section| {
(0..section.len).into_iter().all(|i| machine.main_memory[section.addr + i] == section.content[i])
}).all(|e| e)
}
}
fn string_hex_to_usize(s: &String) -> usize{
fn string_hex_to_usize(s: &String) -> usize {
if s.is_empty() {
return 0;
}
let max_pow = (s.len()-1) as u32;
let mut ret_value: usize = 0;
@ -200,11 +243,11 @@ fn string_hex_to_usize(s: &String) -> usize{
for (i,c )in s.chars().enumerate(){
//println!("Current char :: {} :: Current pow :: {} ::", c, max_pow - (i as u32));
let tmp: usize = (one_hex_to_dec(c) as usize);
let tmp: usize = one_hex_to_dec(c) as usize;
ret_value += base.pow(max_pow - (i as u32))*tmp;
}
return ret_value;
ret_value
}
@ -222,11 +265,10 @@ fn one_hex_to_dec(c: char) -> u8 {
'E' | 'e' => 14,
'F' | 'f' => 15,
_ => {
let ret : u8 = c.to_digit(10).unwrap() as u8;
return ret;
},
}
c.to_digit(10).unwrap() as u8
},
}
}
@ -245,7 +287,7 @@ fn two_hex_to_u8(c1: char, c2: char) -> u8 {
*/
fn test_show_sections_file(){
let file = fs::File::open("test_file_section.txt").expect("Wrong filename");
let reader = io::BufReader::new(file);
let reader = BufReader::new(file);
for line in reader.lines() {
//println!("Tailles de la ligne : {}",
@ -266,18 +308,17 @@ mod tests {
#[test]
fn test_fill_memory(){
let path = "osef".to_string();
let m_c = Mem_Checker::from(&path);
let mut machine = Machine::_init_machine();
let m_c = MemChecker::from(&path).unwrap();
let mut machine = Machine::init_machine();
Mem_Checker::fill_memory_from_Mem_Checker(&m_c, &mut machine);
MemChecker::fill_memory_from_mem_checker(&m_c, &mut machine);
print!("\n Comparing memory from loaded context\n\n");
Mem_Checker::compare_print_m_c_machine(&m_c, &mut machine);
MemChecker::compare_print_m_c_machine(&m_c, &mut machine);
}
#[test]
fn test_enum_start_at_zero(){
let v = vec![1,2,3];
@ -288,10 +329,10 @@ mod tests {
}
#[test]
fn test_create_Mem_Checker(){
fn test_create_mem_checker(){
let path: String = "osef".to_string();
let m_c = Mem_Checker::from(&path);
Mem_Checker::print_Mem_Checker(&m_c);
let m_c = MemChecker::from(&path).unwrap();
MemChecker::print_mem_checker(&m_c);
}
@ -320,12 +361,7 @@ mod tests {
};
let section = Section::from(&section_format);
let mut expected_vec: Vec<u8> = Vec::new();
expected_vec.push(0u8);
expected_vec.push(255u8);
expected_vec.push(10u8);
expected_vec.push(160u8);
expected_vec.push(165u8);
let expected_vec: Vec<u8> = vec![0u8, 255u8, 10u8, 160u8, 165u8];
//println!("Vec from created section {:?}", &section.content);
//println!("Expected vec {:?}", &expected_vec);
@ -333,18 +369,6 @@ mod tests {
assert_eq!(section.content, expected_vec);
}
#[test]
fn test_mod(){
let cond = (0%2) == 0;
assert_eq!(true, cond);
}
#[test]
fn test_mod_2(){
let cond = (1%2) == 1;
assert_eq!(true, cond);
}
#[test]
fn test_hex_1(){
let b = two_hex_to_u8('0', '0');

0
src/simulator/mmu.rs Normal file
View File

View File

@ -2,6 +2,7 @@ pub mod machine;
pub mod decode;
pub mod print;
pub mod mem_cmp;
pub mod loader;
pub mod interrupt;
pub mod global {

View File

@ -13,7 +13,7 @@ const NAMES_OPIW: [&str; 8] = ["addiw", "slliw", "", "", "", "sri", "", ""];
// Register name mapping
const REG_X: [&str; 32] = ["zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
pub const REG_X: [&str; 32] = ["zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
"s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
"t3", "t4", "t5", "t6"];
@ -78,7 +78,7 @@ pub fn print(ins: Instruction, pc: i32) -> String { //TODO pc should be u64
format!("jal\t{},{:x}", REG_X[rd], (pc + ins.imm21_1_signed))
},
RISCV_JALR => {
format!("jalr\t{},{}({})", REG_X[rd], ins.imm12_I_signed, REG_X[rs1])
format!("jalr\t{},{:x}({})", REG_X[rd], ins.imm12_I_signed, REG_X[rs1])
},
RISCV_BR => {
format!("{}\t{},{},{:x}", NAMES_BR[ins.funct3 as usize], REG_X[rs1], REG_X[rs2], pc + (ins.imm13_signed as i32))
@ -139,7 +139,6 @@ pub fn print(ins: Instruction, pc: i32) -> String { //TODO pc should be u64
format!("fnmadd\t{}{}{}{}", REG_F[rd], REG_F[rs1], REG_F[rs2], REG_F[rs3])
},
RISCV_FP => {
let name: &str;
match ins.funct7 {
RISCV_FP_ADD => {
format!("{}\t{}{}{}", "fadd", REG_F[rd], REG_F[rs1], REG_F[rs2])
@ -217,7 +216,7 @@ pub fn print(ins: Instruction, pc: i32) -> String { //TODO pc should be u64
RISCV_SYSTEM => {
"ecall".to_string()
},
_ => todo!("Unknown or currently unsupported opcode") // Change todo! to panic! in the future, I put todo! because there's a lot of opcode currently not implemented
_ => todo!("{:x} opcode non géré pc : {:x}, value : {:x}", ins.opcode, pc, ins.value) // Change todo! to panic! in the future, I put todo! because there's a lot of opcode currently not implemented
}
}

View File

@ -0,0 +1,78 @@
//Nombre maximum de correspondances dans une table des pages
//Cette donnée devra a terme etre recupérée depuis un fichier de configuration
const MaxVirtPages : u64 = 200000;
/* Une table de correspondance propre à un processus
*/
struct TranslationTable{
//capacité de cette table <=> nombre de correspondances possibles
maxNumPages : u64,
//la table en question
//Vec implemente le trait Index, donc un bon choix
pageTable : Vec<PageTableEntry>
}
impl TranslationTable {
fn create() -> TranslationTable {
TranslationTable{
maxNumPages : MaxVirtPages,
page
}
}
}
/* Une correspondance + données sur cette correspondance
*/
struct PageTableEntry{
//true <=> la correspondance est valide et la page est présente dans la ram
valid : bool,
//true <=> la page a été accédée (lecture/ecriture) récemment
U : bool,
//true <=> page modifiée mais non sauvegardée sur disque
M : bool,
//droits d'accès sur cette page
readAllowed : bool,
writeAllowed : bool,
//numero de page physique <=> c'est notre correspondance
physicalPage : i32,
//true <=> cette page doit etre chargée depuis la swap zone du disque
swap : bool,
//a définir plus tard, en relation avec swap
addrDisk : i32,
//mis à 1 par le système quand cette page est impliquée dans une opération d'IO
io : bool
}
impl PageTableEntry{
//Default PageTableEntry Constructor
fn create() -> PageTableEntry {
PageTableEntry {
valid : false,
U : false,
M : false,
readAllowed : false,
writeAllowed : false,
physicalPage : -1i32,
swap : false,
addrDisk : -1i32,
io : false
}
}
}

View File

@ -1,16 +1,24 @@
/// Data structure and definition of a genericsingle-linked LIFO list.
///
/// This is a
#[derive(PartialEq)]
pub struct List<T: PartialEq> {
head: Link<T>,
}
type Link<T> = Option<Box<Node<T>>>;
#[derive(PartialEq)]
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T: PartialEq> List<T> {
/// Create an empty list
pub fn new() -> Self {
List { head: None }
}
@ -69,14 +77,49 @@ impl<T: PartialEq> List<T> {
false
}
/// Remove the item from the list
///
/// Return true if the item has been found, otherwise return false
///
/// Worst-case complexity is O(n)
pub fn remove(&mut self, item: T)-> bool {
let mut found = false;
let mut tmp_list: List<T> = List::new();
while !self.is_empty() {
let current = self.pop().unwrap();
if current != item {
tmp_list.push(current);
} else {
found = true;
break;
}
}
while !tmp_list.is_empty() {
self.push(tmp_list.pop().unwrap());
}
found
}
/// Return true if the list is empty, false otherwise
pub fn is_empty(&self) -> bool {
self.head.is_none()
}
/// Turn the list into an iterator for use in a for loop per example.
///
/// When you iter using into_iter, elements are remove from the list
pub fn into_iter(self) -> IntoIter<T> {
IntoIter(self)
}
/// Turn the list into an iterator for use in a for loop
///
/// When you iter using this method, elements are dereferenced
pub fn iter(&self) -> Iter<'_, T> {
Iter { next: self.head.as_deref() }
}
/// Same as iter but make the iterator mutable
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut { next: self.head.as_deref_mut() }
}
@ -91,6 +134,7 @@ impl<T: PartialEq> Drop for List<T> {
}
}
/// Iterator structure for use in a for loop, pop elements before returning it
pub struct IntoIter<T: PartialEq>(List<T>);
impl<T: PartialEq> Iterator for IntoIter<T> {
@ -101,6 +145,7 @@ impl<T: PartialEq> Iterator for IntoIter<T> {
}
}
/// Iterator structure for use in a for loop, dereference before returning it
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
@ -115,6 +160,7 @@ impl<'a, T> Iterator for Iter<'a, T> {
}
}
/// Same as Iter structure, returned item are mutable
pub struct IterMut<'a, T> {
next: Option<&'a mut Node<T>>,
}

4
test/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignoring dump files
*.dump
*.o
target

21
test/Makefile Normal file
View File

@ -0,0 +1,21 @@
TOPDIR=.
include $(TOPDIR)/Makefile.config
#
# Main targets
#
dumps:
$(MAKE) dumps -C riscv_instructions/
mkdir -p ${TOPDIR}/target/dumps/
find . -name '*.dump' -exec mv {} ${TOPDIR}/target/dumps/ \;
user_lib:
$(MAKE) -C userlib/
tests: user_lib
$(MAKE) tests -C riscv_instructions/
mkdir -p ${TOPDIR}/target/guac/
find . -name '*.guac' -exec mv {} ${TOPDIR}/target/guac/ \;
clean:
rm -rf $(TOPDIR)/target

View File

@ -11,7 +11,8 @@ RISCV_PREFIX=/opt/riscv/bin/
RISCV_AS = $(RISCV_PREFIX)riscv64-unknown-elf-gcc -x assembler-with-cpp -march=rv64imfd
RISCV_GCC = $(RISCV_PREFIX)riscv64-unknown-elf-gcc
RISCV_LD = $(RISCV_PREFIX)riscv64-unknown-elf-ld
RISCV_OBJDUMP = $(RISCV_PREFIX)riscv64-unknown-elf-objdump
RISCV_OBJCOPY = $(RISCV_PREFIX)riscv64-unknown-elf-objcopy
DUMP_FORMAT = ihex
RISCV_ASFLAGS = $(RISCV_CPPFLAGS)
RISCV_CPPFLAGS = #nil
RISCV_CFLAGS = -Wall $(RISCV_CPPFLAGS) -march=rv64imfd

38
test/Makefile.tests Normal file
View File

@ -0,0 +1,38 @@
include $(TOPDIR)/Makefile.config
USERLIB = $(TOPDIR)/userlib
INCPATH += -I$(TOPDIR) -I$(USERLIB)
LDFLAGS = $(RISCV_LDFLAGS) -T $(USERLIB)/ldscript.lds
ASFLAGS = $(RISCV_ASFLAGS) $(INCPATH)
CFLAGS = $(RISCV_CFLAGS) $(INCPATH)
# Rules
%.o: %.s
$(RISCV_AS) $(ASFLAGS) -c $<
%.o: %.c
$(RISCV_GCC) $(CFLAGS) -c $<
%.dump: %.o
$(RISCV_OBJCOPY) -j .text -O $(DUMP_FORMAT) $< $@
%.guac: %.o
$(RISCV_LD) $(LDFLAGS) $+ -o $@
# Dependencies
.%.d: %.s
@echo Generating dependencies for $<
@$(SHELL) -ec '$(GCC) -x assembler-with-cpp -M $(ASFLAGS) $< \
| sed '\''s/\($*\)\.o[ :]*/\1.o $@ : /g'\'' > $@; \
[ -s $@ ] || rm -f $@'
.%.d: %.c
@echo Generating dependencies for $<
@$(SHELL) -ec '$(GCC) -M $(CFLAGS) $< \
| sed '\''s/\($*\)\.o[ :]*/\1.o $@ : /g'\'' > $@; \
[ -s $@ ] || rm -f $@'
# Targets
#clean:
# rm -rf *.o 2> /dev/null
# rm -rf *.dump 2> /dev/null
# rm -rf *.guac 2> /dev/null

View File

@ -11,7 +11,7 @@ Set the variables to the correct paths for the [RISCV Newlib compilation toolcha
### Exporting objdumps
```
$ make objdumps
$ make dumps
```
### Compiling programs

8
test/machine/memory.txt Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
dumps:
make dumps -C boolean_logic/
make dumps -C jump_instructions/
make dumps -C simple_arithmetics/
tests:
make tests -C boolean_logic/
make tests -C jump_instructions/
make tests -C simple_arithmetics/

View File

@ -0,0 +1,9 @@
TOPDIR = ../..
include $(TOPDIR)/Makefile.tests
dumps: comparisons.dump if.dump switch.dump
tests: comparisons.guac if.guac switch.guac
# Dependances
$(PROGRAMS): % : $(USERLIB)/sys.o $(USERLIB)/libnachos.o %.o

View File

@ -9,7 +9,7 @@ int main() {
} else if (x < y) {
y += 1;
} else {
return;
return 0;
}
}
}

View File

@ -0,0 +1,17 @@
int main() {
int x = 0;
int y = 1;
while (x <= y) {
if (x > y) {
y += 1;
}
if (x == y) {
x += y;
}
if (x < y) {
x += 1;
} else {
return 0;
}
}
}

View File

@ -1,7 +1,7 @@
int main() {
int x = 0;
switch(1) {
switch(x) {
case 1: x = 1; break;
default: return;
default: return 0;
}
}

View File

@ -0,0 +1,6 @@
TOPDIR = ../..
include $(TOPDIR)/Makefile.tests
dumps: jump.dump ret.dump
tests: jump.guac ret.guac

View File

@ -1,7 +1,8 @@
int test() {
return 1;
return 0;
}
int main() {
int x = test();
return x;
}

View File

@ -0,0 +1,6 @@
TOPDIR = ../..
include $(TOPDIR)/Makefile.tests
dumps: unsigned_addition.dump unsigned_division.dump unsigned_multiplication.dump unsigned_substraction.dump
tests: unsigned_addition.guac unsigned_division.guac unsigned_multiplication.guac unsigned_substraction.guac

View File

@ -1,5 +1,8 @@
#include "userlib/syscall.h"
#include "userlib/libnachos.h"
// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 1
void main() {
int main() {
unsigned int x = 0;
unsigned int y = 1;
x = x + y;

View File

@ -1,5 +1,5 @@
// Expecting two variables with a value of two
void main() {
int main() {
unsigned int x = 4;
unsigned int y = 2;
x = x / y;

View File

@ -1,5 +1,5 @@
// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 2
void main() {
int main() {
unsigned int x = 1;
unsigned int y = 2;
x = x * y;

View File

@ -1,5 +1,5 @@
// EXPECTS TWO VARIABLES WITH A VALUE OF UNSIGNED 1
void main() {
int main() {
unsigned int x = 1;
unsigned int y = 1;
x = x - y;

4
test/userlib/Makefile Normal file
View File

@ -0,0 +1,4 @@
TOPDIR = ../
include $(TOPDIR)/Makefile.tests
default: sys.o libnachos.o

61
test/userlib/ldscript.lds Normal file
View File

@ -0,0 +1,61 @@
/*
ldscript for running user programs under Nachos
Sections should be aligned on page boundaries. Here an alignement of
at least 0x2000 is selected, thus supporting pages up to 8KB
large. See addrspace.cc for details.
*/
ENTRY(__start)
SECTIONS
{
/* Skip an area of about 8k, so that NULL pointer dereferences can
be detected */
. += 0x2000;
.sys ALIGN(0x4000) : {
*(.init)
*(.sys)
}
/* Code is aligned on a 16k boundary
Due to the size of the .sys section, the code start address will
presumably be at address 0x4000 */
.text ALIGN(0x400000) : {
_ftext = .;
eprol = .;
*(.text)
*(.fini)
}
etext = .;
_etext = .;
/* Initialized data is aligned on a 16k boundary */
.data ALIGN(0x4000) : {
_fdata = .;
*(.data)
*(.sdata)
}
.rodata ALIGN(0x4000) :
{
*(.rdata)
*(.srodata)
*(.rodata)
}
edata = .;
_edata = .;
/* Non-initialized data is aligned on a 16k boundary */
/* Bss = Block Started by Symbol */
.bss ALIGN(0x4000) : {
*(.bss)
*(.sbss)
*(.scommon)
*(COMMON)
}
end = .;
_end = .;
}

630
test/userlib/libnachos.c Normal file
View File

@ -0,0 +1,630 @@
/*! \file libnachos.c
* \brief Functions of our library, for user programs.
*
* This library only provides some usefull functions for
* programming.
*
* -----------------------------------------------------
* This file is part of the Nachos-RiscV distribution
* Copyright (c) 2022 University of Rennes 1.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details
* (see see <http://www.gnu.org/licenses/>).
* -----------------------------------------------------
*/
#include "libnachos.h"
#include <stdarg.h>
#include <stdint.h>
//----------------------------------------------------------------------
// threadStart()
/*! Makes a thread execute a function or program. This function
// is static, it is called internally by library function threadCreate
// and should not be called directly by user programs. The interest
// of this function is to be able to terminate threads correctly,
// even when the thread to be terminated does not explicitly call
// Exit. threadStart provides the mechanism by which Exit
// is called automatically
//
// \param func is the identificator of the function to execute.
*/
//----------------------------------------------------------------------
static void threadStart(uint64_t func)
{
VoidNoArgFunctionPtr func2;
func2=(VoidNoArgFunctionPtr)func;
// Call the function that actually contains the thread code
(*func2)();
// Call exit, such that there is no return using an empty stack
Exit(0);
}
//----------------------------------------------------------------------
// threadCreate()
/*! Creates a thread and makes it execute a function.
//
// NB : instead of directly executing the required function,
// function threadStart is called so as to ensure
// that the thread will properly exit
// This function must be called instead of calling directly
// the system call newThread
//
// \param name the name of the thread (for debugging purpose)
// \param func is the address of the function to execute.
*/
//----------------------------------------------------------------------
ThreadId threadCreate(char *debug_name, VoidNoArgFunctionPtr func)
{
return newThread(debug_name, (uint64_t)threadStart,(uint64_t)func);
}
//----------------------------------------------------------------------
// n_strcmp()
/*! String comparison
//
// \param s1 is the first string,
// \param s2 is the second one.
// \return an integer greater than, equal to, or less than 0,
// if the first string is greater than, equal to, or less than
// the the second string.
*/
//----------------------------------------------------------------------
int n_strcmp(const char *s1, const char *s2)
{
int comparaison;
int fini=0;
int i=0;
while(!fini) {
if ((s1[i]==0)&&(s2[i]==0)) {
fini=1;
comparaison=0;
}
if (s1[i]<s2[i]) {
fini=1;
comparaison=-1;
}
if(s1[i]>s2[i]) {
fini=1;
comparaison=1;
}
i++;
}
return comparaison;
}
//----------------------------------------------------------------------
// n_strcpy()
/*! String copy
//
// \param dst is where the string is to be copied,
// \param src is where the string to copy is.
// \return dst, if the copy successes, 0 otherwise
*/
//----------------------------------------------------------------------
char *n_strcpy(char *dst, const char *src)
{
int i=0;
int fini=0;
if ((dst!=0)&&(src!=0)) {
while(fini==0) {
if(src[i]=='\0') fini=1;
dst[i]=src[i];
i++;
}
return dst;
}
else return 0;
}
//----------------------------------------------------------------------
// n_strlen()
/*! Gives the number of bytes in a string, not including the
// terminating null character.
//
// \param c is a pointer onto a string.
// \return the length of the string.
*/
//----------------------------------------------------------------------
size_t n_strlen(const char *s)
{
size_t i=0;
while (s[i] != 0) i++;
return i;
}
//----------------------------------------------------------------------
// n_strcat()
/*! Appends a copy of a string, including null character, to the end
// of another string. Enough memory has to be available in the
// destination string.
//
// \param dst is a pointer onto the string where the other string
// will be appended.
// \param src is the string to append.
// \return the pointer string dst.
*/
//----------------------------------------------------------------------
char *n_strcat(char *dst, const char *src)
{
int i,j,k;
i=(int)n_strlen(dst);
j=(int)n_strlen(src);
for(k=i;k<=j+i;k++) {
dst[k]=src[k-i];
}
return dst;
}
//----------------------------------------------------------------------
// n_toupper()
/*! Gives the upper-case letter corresponding to the lower-case
// letter passed as parameter.
//
// \param c is the ASCII code of the letter to transform.
// \return the corresponding upper-case letter
*/
//----------------------------------------------------------------------
int n_toupper(int c)
{
if((c>='a')&&(c<='z'))
return c+('A'-'a');
else return c;
}
//----------------------------------------------------------------------
// n_tolower()
/*! Gives the lower-case letter corresponding to the upper-case
// letter passed as parameter
//
// \param c is the ASCII code of the letter to transform.
// \return the corresponding lower-case letter
*/
//----------------------------------------------------------------------
int n_tolower(int c)
{
if((c<='Z')&&(c>='A'))
return c+('a'-'A');
else return c;
}
//----------------------------------------------------------------------
// n_atoi()
/*! String to integer conversion.
//
// \param c is a pointer onto a string.
// \return the corresponding value
*/
//----------------------------------------------------------------------
int n_atoi(const char *str)
{
int i=0;
int fini=0;
int val=0;
int negative = 0;
if (str[i] == '-') {
negative = 1; i=1;
}
while(!fini)
{
if(str[i]==0 || str[i]<'0' || str[i]>'9')
fini=1;
else
{
val*=10;
val+=str[i]-'0';
i++;
}
}
if (negative) return(-val); else return val;
}
//----------------------------------------------------------------------
// n_memcmp()
/*! Memory comparison.
//
// \param s1 is the first memory area,
// \param s2 is the second memory area.
// \param n size in bytes of the area to be compared.
// \return an integer less than, equal to, or greater than 0,
// according as s1 is lexicographically less than, equal to,
// or greater than s2 when taken to be unsigned characters.
//
*/
//----------------------------------------------------------------------
int n_memcmp(const void *s1, const void *s2, size_t n)
{
unsigned char* c1=(unsigned char*)s1;
unsigned char* c2=(unsigned char*)s2;
int comparaison=0;
int fini=0;
int i=0;
while ((!fini)&&(i<n)) {
if (c1[i]<c2[i]) {
fini=1;
comparaison=-1;
}
if (c1[i]>c2[i]) {
fini=1;
comparaison=1;
}
i++;
}
return comparaison;
}
//----------------------------------------------------------------------
// n_memcpy()
/*! Memory copy.
//
// \param s1 is where the elements are to be copied,
// \param s2 is the memory area to copy.
// \param n size in bytes of the area to be copied.
// \return the memory area where the copy has been done.
*/
//----------------------------------------------------------------------
void *n_memcpy(void *s1, const void *s2, size_t n)
{
unsigned char* c1=(unsigned char*)s1;
unsigned char* c2=(unsigned char*)s2;
int i=0;
if ((c1!=0)&&(c2!=0)) {
while(i<n) {
c1[i]=c2[i];
i++;
}
return (void *)c1;
}
else return 0;
}
//----------------------------------------------------------------------
// n_memset()
/*! Sets the first n bytes of a memory area to a value (converted to
// an unsigned char).
//
// \param s is the memory area to transform,
// \param c is the value wanted,
// \param n is the number of bytes to put at c.
// \return s.
*/
//----------------------------------------------------------------------
void *n_memset(void *s, int c, size_t n)
{
unsigned char* c1=(unsigned char*)s;
int i;
for (i=0;i<n;i++) {
c1[i]=c;
}
return (void *)c1;
}
//----------------------------------------------------------------------
// n_dumpmem()
/*! Dumps on the string the n first bytes of a memory area
// (used for debugging)
//
// \param addr address of the memory area
// \param len number of bytes to be dumped
*/
//----------------------------------------------------------------------
void n_dumpmem(char *addr, int len)
{
#define TOHEX(x) \
({ char __x = (x); if(__x < 10) __x+='0'; else __x='a'+(__x-10) ; __x; })
int i;
for (i = 0 ; i < len ; i++) {
char s[3];
if ((i%16) == 0)
n_printf("%x\t", (unsigned long)&addr[i]);
else if ((i%8) == 0)
n_printf(" ");
s[0] = TOHEX((addr[i] >> 4) & 0xf);
s[1] = TOHEX(addr[i] & 0xf);
s[2] = '\0';
n_printf("%s ", s);
if ((((i+1)%16) == 0) || (i == len-1))
n_printf("\n");
}
}
#define PUTCHAR(carac) \
do { \
if (result < len-1) *buff++ = carac;\
result++; \
} while (0)
//----------------------------------------------------------------------
// n_vsnprintf()
/*! Build a string according to a specified format (internal function)
//
// Nachos vsnprintf accepts:
// %c to print a character,
// %s, to print a string,
// %d, to print an integer,
// %x, to print an integer in hexa
// %lx %ld same for 64-bit values
// %f, to print a floating point value
//
// \param buff the destination buffer to generate the string to
// \param len the size of buff, determines the number max of
// characters copied to buff (taking the final \0 into account)
// \param format the string to parse
// \param ap parameters to print
//
// \return the number of characters formatted (NOT including \0),
// that is, the number of characters that would have been written
// to the buffer if it were large enough. -1 on error.
*/
//----------------------------------------------------------------------
static int n_vsnprintf(char *buff, int len, const char *format, va_list ap)
{
int i, result;
if (!buff || !format || (len < 0)) {
return -1;
}
result = 0;
for (i=0 ; format[i] != '\0' ; i++) {
switch (format[i]) {
case '%':
i++;
switch(format[i]) {
case '%': {
PUTCHAR('%');
break;
}
case 'i':
case'd': {
int integer = (int) va_arg(ap,int);
int cpt2 = 0;
char buff_int[11];
if (integer<0) {PUTCHAR('-');
}
do {
int m10 = integer%10;
m10 = (m10 < 0)? -m10:m10;
buff_int[cpt2++]=(char)('0'+ m10);
integer=integer/10;
} while(integer!=0);
for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) {
PUTCHAR(buff_int[cpt2]);
}
break;
}
case 'l': {
i++;
switch(format[i]) {
case 'd': {
long longer = va_arg(ap,long);
int cpt2 = 0;
char buff_long[20];
if (longer<0) {
PUTCHAR('-');
}
do {
int m10 = longer%10;
m10 = (m10 < 0)? -m10:m10;
buff_long[cpt2++]=(char)('0'+ m10);
longer=longer/10;
} while(longer!=0);
for (cpt2 = cpt2 - 1 ; cpt2 >= 0 ; cpt2--) {
PUTCHAR(buff_long[cpt2]);
}
break;
}
case 'x': {
uint64_t hexa = va_arg(ap,long);
uint64_t nb;
uint32_t i, had_nonzero = 0;
for (i=0 ; i < 16 ; i++) {
nb = (hexa << (i*4));
nb = (nb >> 60);
nb = nb & 0x000000000000000f;
// Skip the leading zeros
if (nb == 0) {
if (had_nonzero) {
PUTCHAR((uint8_t)'0');
}
}
else {
had_nonzero = 1;
if (nb < 10)
PUTCHAR((uint8_t)'0'+(uint8_t)nb);
else
PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10));
}
}
if (! had_nonzero)
PUTCHAR((uint8_t)'0');
break;
}
default: {
PUTCHAR('%');
PUTCHAR('l');
PUTCHAR(format[i]);
break;
}
}
break;
}
case 'c': {
int value = va_arg(ap,int);
PUTCHAR((char)value);
break;
}
case 's': {
char *string = va_arg(ap,char *);
if (! string)
string = "(null)";
for( ; *string != '\0' ; string++)
PUTCHAR(*string);
break;
}
case 'x': {
uint32_t hexa = va_arg(ap,int);
uint32_t nb;
uint32_t i, had_nonzero = 0;
for (i=0 ; i < 8 ; i++) {
nb = (hexa << (i*4));
nb = (nb >> 28);
nb = nb & 0x0000000f;
// Skip the leading zeros
if (nb == 0) {
if (had_nonzero)
PUTCHAR((uint8_t)'0');
}
else {
had_nonzero = 1;
if (nb < 10)
PUTCHAR((uint8_t)'0'+(uint8_t)nb);
else
PUTCHAR((uint8_t)'a'+(uint8_t)(nb-10));
}
}
if (! had_nonzero)
PUTCHAR((uint8_t)'0');
break;
}
/*case 'f': {
// Very simple routine to print floats as xxxx.yyyyy
// Not very good (unable to print large numbers)
// If anyone wants to re-write it, feel free ...
double f = (double) va_arg(ap,double);
int cpt2, j;
char buff_float[200];
long ient,idec;
if (f<0) {
PUTCHAR('-');
f = -f;
}
ient = (int)f;
// 100000 = print 5 digits max
idec = (int)((f - ((double)ient))*100000);
// Round up
if ( f - ((double)ient) - ((double)idec)/100000.0 >= 0.5E-5)
idec ++;
cpt2 = 0;
// Print digits after the '.'
for (j=0 ; j<5 ; j++) {
buff_float[cpt2++]=(char)('0'+(idec%10));
idec=idec/10;
}
buff_float[cpt2++] = '.';
// Print digits before the '.'
do {
buff_float[cpt2++]=(char)('0'+ (ient%10));
ient=ient/10;
} while (ient!=0);
for(j = cpt2 - 1 ; j >= 0 ; j--)
PUTCHAR(buff_float[j]);
break;
}
*/
default:
PUTCHAR('%');
PUTCHAR(format[i]);
}
break;
default:
PUTCHAR(format[i]);
}
}
*buff = '\0';
return result;
}
//----------------------------------------------------------------------
// n_snprintf()
/*! Build a string according to a specified format
//
// Nachos snprintf accepts:
// %c to print a character,
// %s, to print a string,
// %d, to print an integer,
// %x, to print a string in hexa
// %f, to print a floating point value
//
// \param buff the destination buffer to generate the string to
// \param len the size of buff, determines the number max of
// characters copied to buff (taking the final \0 into account)
// \param format the string to parse
// \param ... the (variable number of) arguments
//
// \return the number of characters formatted (NOT including \0),
// that is, the number of characters that would have been written
// to the buffer if it were large enough. -1 on error.
*/
//----------------------------------------------------------------------
int n_snprintf(char * buff, int len, const char *format, ...){
va_list ap;
va_start(ap, format);
len = n_vsnprintf(buff, len, format, ap);
va_end(ap);
return len;
}
//----------------------------------------------------------------------
// n_printf()
/*! Print to the standard output parameters.
//
// Nachos printf accepts:
// %c to print a character,
// %s, to print a string,
// %d, to print an integer,
// %x, to print a string in hexa
// %ld, %lx, same for 64-bit values
// %f, to print a floating point value
//
// \param parameters to print,
// \param type of print.
*/
//----------------------------------------------------------------------
void n_printf(const char *format, ...){
va_list ap;
char buff[200];
int len;
va_start(ap, format);
len = n_vsnprintf(buff, sizeof(buff), format, ap);
va_end(ap);
if (len >= sizeof(buff)) {
len = sizeof(buff) - 1;
}
if (len > 0) {
Write(buff,len,CONSOLE_OUTPUT);
}
}
//----------------------------------------------------------------------
// n_read_int()
/*!
// Very basic minimalist read integer function, no error
// checking...
*/
//----------------------------------------------------------------------
int n_read_int(void) {
char buff[200];
Read(buff,200,CONSOLE_INPUT);
return n_atoi(buff);
}

87
test/userlib/libnachos.h Normal file
View File

@ -0,0 +1,87 @@
/*! \file libnachos.h
\brief Function structures for programs
Libnachos proposes several 'libc-like' functions
for:
Input-Output operations,
String operations,
Memory operations,
System calls are defined in kernel/syscalls.h
Nachos-libc functions are prefixed by 'n' to avoid
any confusion with standard libc functions.
* -----------------------------------------------------
* This file is part of the Nachos-RiscV distribution
* Copyright (c) 2022 University of Rennes 1.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details
* (see see <http://www.gnu.org/licenses/>).
* -----------------------------------------------------
*/
#include "userlib/syscall.h"
typedef void (*VoidNoArgFunctionPtr)();
typedef unsigned int size_t;
// Thread management
// ----------------------------
ThreadId threadCreate(char * debug_name, VoidNoArgFunctionPtr func);
// Input/Output operations :
// ------------------------------------
// Print on the standard output specified parameters.
void n_printf(const char *format, ...);
// Format <buff> (of max length <len>) according to the format <format>
int n_snprintf(char * buff, int len, const char *format, ...);
// Read an integer on the standard input
int n_read_int(void);
// String operations :
// -------------------
// Compare two strings byte by byte.
int n_strcmp(const char *s1, const char *s2);
// Copy a string.
char* n_strcpy(char *dst, const char *src);
// Return the number of bytes in a string.
size_t n_strlen(const char *s);
// appends a copy of a string, to the end of another string.
char* n_strcat(char *dst, const char *src);
// Return a upper-case letter,
// equivalent to the lower-case letter given.
int n_toupper(int c);
// Return a lower-case letter,
// equivalent to the upper-case letter given.
int n_tolower(int c);
// Convert a string in integer.
int n_atoi(const char *str);
// Concerning memory area operations :
// -----------------------------------
// Compare two memory area, looking at the first n bytes .
int n_memcmp(const void *s1, const void *s2, size_t n);
// Copy n byte from an memory area to another.
void* n_memcpy(void *s1, const void *s2, size_t n);
// Set the first n bytes in a memory area to a specified value.
void* n_memset(void *s, int c, size_t n);

343
test/userlib/sys.s Normal file
View File

@ -0,0 +1,343 @@
/* Start.s
* Assembly language assist for user programs running on top of Nachos.
*
* Since we don't want to pull in the entire C library, we define
* what we need for a user program here, namely Start and the system
* calls.
*
* -----------------------------------------------------
* This file is part of the BurritOS distribution
* Copyright (c) 2022 University of Rennes 1.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details
* (see see <http://www.gnu.org/licenses/>).
* -----------------------------------------------------
*/
#define IN_ASM
#include "userlib/syscall.h"
// Equivalent to ".text", but with a different name, in order
// to be correctly handled by the ldscript
.section .sys,"ax",@progbits
.align 2
/* -------------------------------------------------------------
* __start
* Initialize running a C program, by calling "main".
*
* NOTE: This has to be first, so that it gets loaded at location 0.
* The Nachos kernel always starts a program by jumping to location 0.
* -------------------------------------------------------------
*/
.globl __start
.type __start, @function
__start:
/* Call the program entry point */
call main
li a0, 0
call Exit
jr ra /* if we return from main, exit(0) */
/* -------------------------------------------------------------
* System call stubs:
* Assembly language assist to make system calls to the Nachos kernel.
* There is one stub per system call, that places the code for the
* system call into register r10, and leaves the arguments to the
* system call alone (in other words, arg1 is in r12, arg2 is
* in r13, arg3 is in r14, arg4 is in r15)
*
* The return value is in r10. This follows the standard C calling
* convention on the RISC-V.
* -------------------------------------------------------------
*/
.globl Halt
.type __Halt, @function
Halt:
addi a7,zero,SC_HALT
ecall
jr ra
.globl Exit
.type __Exit, @function
Exit:
addi a7,zero,SC_EXIT
ecall
jr ra
.globl Exec
.type __Exec, @function
Exec:
addi a7,zero,SC_EXEC
ecall
jr ra
.globl Join
.type __Join, @function
Join:
addi a7,zero,SC_JOIN
ecall
jr ra
.globl Create
.type __Create, @function
Create:
addi a7,zero,SC_CREATE
ecall
jr ra
.globl Open
.type __Open, @function
Open:
addi a7,zero,SC_OPEN
ecall
jr ra
.globl Read
.type __Read, @function
Read:
addi a7,zero,SC_READ
ecall
jr ra
.globl Write
.type __Write, @function
Write:
addi a7,zero,SC_WRITE
ecall
jr ra
.globl Seek
.type __Seek, @function
Seek:
addi a7,zero,SC_SEEK
ecall
jr ra
.globl Close
.type __Close, @function
Close:
addi a7,zero,SC_CLOSE
ecall
jr ra
.globl FSList
.type __FSList, @function
FSList:
addi a7,zero,SC_FSLIST
ecall
jr ra
.globl newThread
.type __newThread, @function
newThread:
addi a7,zero,SC_NEW_THREAD
ecall
jr ra
.globl Remove
.type __Remove, @function
Remove:
addi a7,zero,SC_REMOVE
ecall
jr ra
.globl Yield
.type __Yield, @function
Yield:
addi a7,zero,SC_YIELD
ecall
jr ra
.globl PError
.type __PError, @function
PError:
addi a7,zero,SC_PERROR
ecall
jr ra
.globl P
.type __P, @function
P:
addi a7,zero,SC_P
ecall
jr ra
.globl V
.type __V, @function
V:
addi a7,zero,SC_V
ecall
jr ra
.globl SemCreate
.type __SemCreate, @function
SemCreate:
addi a7,zero,SC_SEM_CREATE
ecall
jr ra
.globl SemDestroy
.type __SemDestroy, @function
SemDestroy:
addi a7,zero,SC_SEM_DESTROY
ecall
jr ra
.globl SysTime
.type __SysTime, @function
SysTime:
addi a7,zero,SC_SYS_TIME
ecall
jr ra
.globl LockCreate
.type __LockCreate, @function
LockCreate:
addi a7,zero,SC_LOCK_CREATE
ecall
jr ra
.globl LockDestroy
.type __LockDestroy, @function
LockDestroy:
addi a7,zero,SC_LOCK_DESTROY
ecall
jr ra
.globl LockAcquire
.type __LockAquire, @function
LockAcquire:
addi a7,zero,SC_LOCK_ACQUIRE
ecall
jr ra
.globl LockRelease
.type __LockRelease, @function
LockRelease:
addi a7,zero,SC_LOCK_RELEASE
ecall
jr ra
.globl CondCreate
.type __CondCreate, @function
CondCreate:
addi a7,zero,SC_COND_CREATE
ecall
jr ra
.globl CondDestroy
.type __CondDestroy, @function
CondDestroy:
addi a7,zero,SC_COND_DESTROY
ecall
jr ra
.globl CondWait
.type __CondWait, @function
CondWait:
addi a7,zero,SC_COND_WAIT
ecall
jr ra
.globl CondSignal
.type __CondSignal, @function
CondSignal:
addi a7,zero,SC_COND_SIGNAL
ecall
jr ra
.globl CondBroadcast
.type __CondBroadcast, @function
CondBroadcast:
addi a7,zero,SC_COND_BROADCAST
ecall
jr ra
.globl TtySend
.type __TtySend, @function
TtySend:
addi a7,zero,SC_TTY_SEND
ecall
jr ra
.globl TtyReceive
.type __TtyReceive, @function
TtyReceive:
addi a7,zero,SC_TTY_RECEIVE
ecall
jr ra
.globl Mkdir
.type __Mkdir, @function
Mkdir:
addi a7,zero,SC_MKDIR
ecall
jr ra
.globl Rmdir
.type __Rmdir, @function
Rmdir:
addi a7,zero,SC_RMDIR
ecall
jr ra
.globl Mmap
.type __Mmap, @function
Mmap:
addi a7,zero,SC_MMAP
ecall
jr ra
.globl Debug
.type __Debug, @function
Debug:
addi a7,zero,SC_DEBUG
ecall
jr ra

287
test/userlib/syscall.h Normal file
View File

@ -0,0 +1,287 @@
/*! \file syscall.h
\brief Nachos system call interface.
These are Nachos kernel operations
that can be invoked from user programs, by trapping to the kernel
via the "syscall" instruction.
This file is included by user programs and by the Nachos kernel.
Each of these is invoked by a user program by simply calling the
procedure; an assembly language stub stuffs the system call code
into a register, and traps to the kernel. The kernel procedures
are then invoked in the Nachos kernel, after appropriate error checking,
from the system call entry point in exception.cc.
* -----------------------------------------------------
* This file is part of the Nachos-RiscV distribution
* Copyright (c) 2022 University of Rennes 1.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details
* (see see <http://www.gnu.org/licenses/>).
* -----------------------------------------------------
*/
#ifndef SYSCALLS_H
#define SYSCALLS_H
//#include "kernel/copyright.h"
/* system call codes -- used by the stubs to tell the kernel which system call
* is being asked for
*/
#define SC_HALT 0
#define SC_EXIT 1
#define SC_EXEC 2
#define SC_JOIN 3
#define SC_CREATE 4
#define SC_OPEN 5
#define SC_READ 6
#define SC_WRITE 7
#define SC_SEEK 8
#define SC_CLOSE 9
#define SC_NEW_THREAD 10
#define SC_YIELD 11
#define SC_PERROR 12
#define SC_P 13
#define SC_V 14
#define SC_SEM_CREATE 15
#define SC_SEM_DESTROY 16
#define SC_LOCK_CREATE 17
#define SC_LOCK_DESTROY 18
#define SC_LOCK_ACQUIRE 19
#define SC_LOCK_RELEASE 20
#define SC_COND_CREATE 21
#define SC_COND_DESTROY 22
#define SC_COND_WAIT 23
#define SC_COND_SIGNAL 24
#define SC_COND_BROADCAST 25
#define SC_TTY_SEND 26
#define SC_TTY_RECEIVE 27
#define SC_MKDIR 28
#define SC_RMDIR 29
#define SC_REMOVE 30
#define SC_FSLIST 31
#define SC_SYS_TIME 32
#define SC_MMAP 33
#define SC_DEBUG 34
#ifndef IN_ASM
/* The system call interface. These are the operations the Nachos
* kernel needs to support, to be able to run user programs.
*
*/
typedef int t_error;
/* Stop Nachos, and print out performance stats */
void Halt();
/* Return the time spent running Nachos */
/*! \brief Defines the Nachos basic time unit */
typedef struct {
long seconds;
long nanos;
} Nachos_Time;
void SysTime(Nachos_Time *t);
/* Address space control operations: Exit, Exec, and Join */
/* This user program is done (status = 0 means exited normally). */
void Exit(int status);
/* A unique identifier for a thread executed within a user program */
typedef unsigned long ThreadId;
/* Run the executable, stored in the Nachos file "name", and return the
* master thread identifier
*/
ThreadId Exec(char *name);
/* Create a new thread in the current process
* Return thread identifier
*/
ThreadId newThread(char * debug_name, int func, int arg);
/* Only return once the the thread "id" has finished.
*/
t_error Join(ThreadId id);
/* Yield the CPU to another runnable thread, whether in this address space
* or not.
*/
void Yield();
/*! Print the last error message with the personalized one "mess" */
void PError(char *mess);
/* File system operations: Create, Open, Read, Write, Seek, Close
* These functions are patterned after UNIX -- files represent
* both files *and* hardware I/O devices.
*
* If this assignment is done before doing the file system assignment,
* note that the Nachos file system has a stub implementation, which
* will work for the purposes of testing out these routines.
*/
/* A unique identifier for an open Nachos file. */
typedef unsigned long OpenFileId;
/* when an address space starts up, it has two open files, representing
* keyboard input and display output (in UNIX terms, stdin and stdout).
* Read and Write can be used directly on these, without first opening
* the console device.
*/
#define CONSOLE_INPUT 0
#define CONSOLE_OUTPUT 1
/* Create a Nachos file, with "name" */
t_error Create(char *name,int size);
/* Open the Nachos file "name", and return an "OpenFileId" that can
* be used to read and write to the file.
*/
OpenFileId Open(char *name);
/* Write "size" bytes from "buffer" to the open file. */
t_error Write(char *buffer, int size, OpenFileId id);
/* Read "size" bytes from the open file into "buffer".
* Return the number of bytes actually read -- if the open file isn't
* long enough, or if it is an I/O device, and there aren't enough
* characters to read, return whatever is available (for I/O devices,
* you should always wait until you can return at least one character).
*/
t_error Read(char *buffer, int size, OpenFileId id);
/* Seek to a specified offset into an opened file */
t_error Seek(int offset, OpenFileId id);
#ifndef SYSDEP_H
/* Close the file, we're done reading and writing to it. */
t_error Close(OpenFileId id);
#endif // SYSDEP_H
/* Remove the file */
t_error Remove(char* name);
/******************************************************************/
/* system calls concerning directory management */
/* Create a new repertory
Return a negative number if an error ocurred.
*/
t_error Mkdir(char* name);
/* Destroy a repertory, which must be empty.
Return a negative number if an error ocurred.
*/
t_error Rmdir(char* name);
/* List the content of NachOS FileSystem */
t_error FSList();
/******************************************************************/
/* User-level synchronization operations : */
/* System calls concerning semaphores management */
typedef unsigned long SemId;
/* Create a semaphore, initialising it at count.
Return a Semid, which will enable to do operations on this
semaphore */
SemId SemCreate(char * debug_name, int count);
/* Destroy a semaphore identified by sema.
Return a negative number if an error occured during the destruction */
t_error SemDestroy(SemId sema);
/* Do the operation P() on the semaphore sema */
t_error P(SemId sema);
/* Do the operation V() on the semaphore sema */
t_error V(SemId sema);
/* System calls concerning locks management */
typedef unsigned long LockId;
/* Create a lock.
Return an identifier */
LockId LockCreate(char * debug_name);
/* Destroy a lock.
Return a negative number if an error ocurred
during the destruction. */
t_error LockDestroy(LockId id);
/* Do the operation Acquire on the lock id.
Return a negative number if an error ocurred. */
t_error LockAcquire(LockId id);
/* Do the operation Release on the lock id.
Return a negative number if an error ocurred.
*/
t_error LockRelease(LockId id);
/* System calls concerning conditions variables. */
typedef unsigned long CondId;
/* Create a new condition variable */
CondId CondCreate(char * debug_name);
/* Destroy a condition variable.
Return a negative number if an error ocurred.
*/
t_error CondDestroy(CondId id);
/* Do the operation Wait on a condition variable.
Returns a negative number if an error ocurred.
*/
t_error CondWait(CondId cond);
/* Do the operation Signal on a condition variable (wake up only one thread).
Return a negative number if an error ocurred.
*/
t_error CondSignal(CondId cond);
/* Do the operation Signal on a condition variable (wake up all threads).
Return a negative number if an error ocurred.
*/
t_error CondBroadcast(CondId cond);
/******************************************************************/
/* System calls concerning serial port and console */
/* Send the message on the serial communication link.
Returns the number of bytes successfully sent.
*/
int TtySend(char *mess);
/* Wait for a message comming from the serial communication link.
The length of the buffer where the bytes will be copied is given as a parameter.
Returns the number of characters actually received.
*/
int TtyReceive(char *mess,int length);
/* Map an opened file in memory. Size is the size to be mapped in bytes.
*/
void *Mmap(OpenFileId f, int size);
/* For debug purpose
*/
void Debug(int param);
#endif // IN_ASM
#endif // SYSCALL_H

View File

@ -1,8 +0,0 @@
0
0
FF FF
F4A12200
A0 0A
01022B
0B 0F
FFACBC5CEF

View File

@ -1,14 +0,0 @@
TOPDIR=.
include $(TOPDIR)/Makefile.config
#
# Main targets
#
objdumps:
$(MAKE) -C riscv_instructions
programs:
$(MAKE) -C programs
clean:
rm -rf $(TOPDIR)/target

View File

@ -1,24 +0,0 @@
include $(TOPDIR)/Makefile.config
COVERAGE = $(TOPDIR)/riscv_instructions
AS = $(RISCV_AS) -c
GCC = $(RISCV_GCC)
LD = $(RISCV_LD)
INCPATH += -I$(TOPDIR) -I$(COVERAGE)
ASFLAGS = $(RISCV_ASFLAGS) $(INCPATH)
CFLAGS = $(RISCV_CFLAGS) $(INCPATH)
# Rules
%.a:
$(AR) rcv $@ $^
%.o: %.c
$(GCC) $(CFLAGS) -c $<
%.o: %.s
$(AS) $(ASFLAGS) -c $<
$(PROGRAMS):
$(LD) $+ -o $@

View File

@ -1,2 +0,0 @@
TOPDIR = ../
include $(TOPDIR)/Makefile.objdumps