# 卒研経過報告 #9 Rustによる組み込みプログラミング マイコンWio terminalを用いたRustとC言語の信頼性,安全性の比較検証、考察 [先週のレジュメ](https://hackmd.io/@Fteam/B154mXBLt) ## 研究の目的 この研究ではこれまで主に組込みソフトウエア開発に使われてきたC/C++と それに替わる有効な選択肢として、注目されているRust言語との 信頼性や安全性など様々な面から比較検証を行う。 ## 検証 #### 1.コピー先とコピー元が重なった時のエラー strcpy()の停止条件'\0'がコピーによって消えるため,無限ループに陥り,不正なアドレスへの書き込みを行う. Hをeの場所にコピーする、eはHになってるので、次にHをlの場所にコピーするそのままヌルが消えて HHHHHH...と無限にコピーし続ける :::spoiler コード ```c= #include<stdio.h> char* strcpy(char *dst, char *src){ int i = 0; while(1){ dst[i] = src[i]; if(src[i] == '\0') break; i++; } return dst; } int main(void){ char str[] = "Hello!"; strcpy(str +1, str); printf("%s\n", str); return 0; } ``` ``` Segmentation fault ``` ```rust= fn main(){ //let str1: &str = "Hello!"; //let str2 = String::from("Alice"); //let str2 = str1.clone(); //println!("str1:{},{:p}", str1, &str1); //println!("str2:{},{:p}", str2, &str2); let mut msg = String::from("My name is Koyasu."); //let slice1 = &msg[..7]; //let slice2 = &msg[11..msg.len()]; let msg2 = &msg[8..msg.len()]; msg = msg2.to_string(); //スライスが参照するデータの代入 println!("msg:{}", msg); } ``` **疑問** - rustの文字列はcharの配列としてメモリに格納されているわけではない - Rustの文字列は、添え字アクセスをサポートしていない ```rust=2 let s1 = String::from("hello"); let h = s1[0]; //error ``` - スライスではrustで重複コピーした際にcの例のように一部を重複させることはできず参照するデータの代入しかできないのではないか - 生ポインタはrustの所有権システムで実現できないような柔軟な構造を作ることができるが,unsafe()ブロックを用いることが必須であり,このブロック内で記述したコードはプログラマの責任となるためrustによる安全性は保障されないのではないか ::: #### 2.未初期化メモリの使用(修正) :::spoiler コード ```c= #include<stdio.h> int main(void){ int a; printf("%d\n", a); } ``` ```rust= fn main() { let a:i8; println!("{}", a); } ``` エラーコード ``` Compiling playground v0.0.1 (/playground) error[E0381]: borrow of possibly-uninitialized variable: `a` --> src/lib.rs:4:20 | 4 | println!("{}", a); | ^ use of possibly-uninitialized `a` For more information about this error, try `rustc --explain E0381`. error: could not compile `playground` due to previous error ``` ::: c言語では値の初期化なしにコンパイル,実行を行っても特にエラーは出なかった.プログラムが増えると初期化されていないメモリへのアクセスはバグにつながる恐れがある.Rustではこれらのプログラマのミスをコンパイル時に検出する. #### 3.バッファオーバーフロー(修正) c言語における危険な関数`gets()` ,`scanf()` :::spoiler コード gets()の例 ```c= #include<stdio.h> int main(void){ char s[5]; gets(s); printf("%s",s); } ``` scanf()の例 ```c=4 char s[5]; scanf("%s", s); ``` ``` aaaaaaaa *** stack smashing detected ***: <unknown> terminated 中止 ``` ```rust= use std::io; fn main() { let mut s = String::new();//可変変数sを宣言し,空の文字列で初期化 io::stdin().read_line(&mut s).unwrap();//一行分の文字列を読み込みsに保存 println!("{}", s); } ``` ::: これらの関数は変数の要素数を間違えるとバッファオーバーフローが発生してしまい,改善策はプログラマが気を付けてコーディングする他ない.rustでは変数に文字列を代入せず,スタック領域に確保した文字列のメモリへのポインタを示している. #### 4.NULLポインタの参照 NULLポインタを参照する. :::spoiler コード ```c= #include<stdio.h> int main(void){ int *p = NULL; int a; a = p[0]; return 0; } ``` ``` Segmentation fault ``` rustでの安全性主張例は次回発表 ::: c言語で未定義動作であるNULLポインタの参照を行うと,セグフォエラーに陥る. rustではこのようなことは発生しない. ## 参考文献 [1] rustプログラミング入門 オーム社 [2] rust入門 増田智明著 [公式ドキュメント](https://doc.rust-jp.rs/book-ja/ch08-02-strings.html) [](https://teratail.com/questions/56792) [](https://prev.rust-lang.org/ja-JP/faq.html) [](https://marycore.jp/prog/c-lang/dangerous-gets-safely-gets/) [](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%83%B3) [](https://ja.wikipedia.org/wiki/Scanf#%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%83%B3) [](http://www.kumikomi.net/archives/2009/01/02bug.php?page=4) [](https://www.tohoho-web.com/ex/rust.html) :::spoiler メモ #### 5.配列外の値へ不正アクセス 配列番号の外の値を指定し,メモリ破壊を起こすエラー ```c= #include<stdio.h> int main(void){ int a[3] = {0, 1, 2}; int b[3] = {3, 4, 5}; a[3] = 99; // bug for(int i = 0; i < 3; i++) printf("a[%d] = %d\n", i, a[i]); for(int i = 0; i < 3; i++) printf("b[%d] = %d\n", i, b[i]); return 0; } ``` ```rust= fn main(){ let mut a = [0, 1, 2]; let /*mut*/ b = [3, 4, 5]; a[3] = 99; for i in 0..3{println!("a[{}] = {}", i, a[i]);} for i in 0..3{println!("b[{}] = {}", i, b[i]);} } ``` まずc言語のソースコードを確認すると,宣言されていない配列にアクセスしようとしておりメモリ破壊が起こることが容易に想像できる.しかしc言語のコンパイラはエラーをださず,結果として他配列に影響を及ぼしてしまっていることが確認できる.これが重要なシステムであると仮定するといかに危険な行為であるか理解できる.rustではこのような事態に陥る前に予防線を張っており,配列番号の外の数字を指定すると停止してくれる. ::: :::spoiler メモ [](https://qiita.com/nsnonsugar/items/be8a066c6627ab5b052a) ```=rust use std::thread; use std::time::Duration; static count:i8=0; fn main() { let i=0; thread::spawn(|| { for i in 1..100 { count+=1; thread::sleep(Duration::from_millis(1)); } }); thread::spawn(|| { for i in 1..100 { count+=1; thread::sleep(Duration::from_millis(1)); } }); println!("{}",count); } ``` ```=C++ #include <thread> #include <cstdio> #include <cstdint> uint32_t count; void ThreadA(){ for(int i=0;i<10;++i){ ++count; } } void ThreadB(){ for(int i=0;i<100;++i){ ++count; } } int main(){ count = 0; std::thread th_a(ThreadA); std::thread th_b(ThreadB); th_a.join(); th_b.join(); printf("count=%d\n",count); return 0; } ``` :::