星期日, 12月 17, 2023

Rust - 參數輸入與 Exit Code

初次看到 Rust 的 main function 時

fn main() {
	println!("Hello World");
}

還以為是與 C 一樣可以透過在 main 在加參數來取得參數。但實際上像 Python 一樣需要透過別的 crate 來處理。

底下是一個輸出所有輸入參數的範例

use std::env;

fn main() {
	let args: Vec<String> = env::args().collect();
    
    for arg in args {
    	println!("{}", arg);
    }
}
env::args() 會取得 iterator,因此範例也可以簡單寫成
use std::env;

fn main() {
    for arg in env::args() {
    	println!("{}", arg);
    }
}
而回傳 Exit Code 的部份, C 是在 main function 回傳 int,而 Rust 是回傳有實做 Termination trait 的物件 (實做了 fn report() -> ExitCode) 即可

而在 Terminalion Trait 中,為以下五種型別定義了實做

  • Infallible
  • !
  • ()
  • ExitCode
  • <T: Termination, E: Debug> Termination for Result<T, E>

前兩者我目前不懂,對於後三者來說

一律回傳成功

fn main() -> () {
	// 表示回傳 ExitCode::SUCCESS
}

回傳指定 ExitCode

use std::process::ExitCode;

fn main() -> ExitCode {
    return ExitCode::SUCCESS;
}

若想返回任意值,可以用

use std::process::ExitCode;

fn main() -> ExitCode {
    return ExitCode::from(0);
}

執行成功時,使用 Ok(T) 中的 T 的 report 結果,若是錯誤則輸出 Err(E) 中的 Debug 訊息並回傳 ExitCode::FAILURE

use std::fs::File;

fn main() -> Result<(), std::io::Error> {
   let f = File::open("bar.txt");

   match f {
      Ok(_) => {
         return Ok(());
      },
      Err(e) => {
         return Err(e);
      },
   };
}
這種 Return 執行錯誤會得到 stderr 的輸出
Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }
上一個 code 也可以寫成
use std::fs::File;

fn main() -> Result<(), std::io::Error> {
	let f = File::open("bar.txt")?;
	// ? 若執行結果是 Err, 直接 return 該 Err
	Ok(())
}
若想隨時中止,可以使用 std::process:exit
fn main() {
   std::process::exit(1);
}

星期日, 12月 10, 2023

在 Linux 上使用 RX6600 跑 whisper

出於個人需求,需要使用 whipser 將影片中的對話轉成文字

whisper 是由 OpenAI 出品的語音辨識 Model,運行在 PyTorch 這個機器學習框架上。>

在這裡我使用 venv 來進行安裝

$ python3 -mvenv whisper
$ soruce whisper bin/activate

然後到 PyTorch 官網查看安裝 PyTorch With ROCM 的安裝方法,ROCM 是由 AMD 開發與 NVIDIA CUDA 對標的開發平台。

在這裡我們運行

(whisper) $ pip3 install torch torchvision torchaudio \
	--index-url https://download.pytorch.org/whl/rocm5.6

安裝完畢後嘗試執行,結果 Segmentation Fault 了

(whisper) $ whisper --language zh input.mkv
segmentation fault (coredumpd)

可能跟 RX6600 在 ROCM 只支援 Windows 有關,經過一番搜尋後,發現使用 HSA_OVERRIDE_GFX_VERSION=10.3.0 可以順利運行

(whisper) $ HSA_OVERRIDE_GFX_VERSION=10.3.0 whisper --language zh input.mkv

根據 https://llvm.org/docs/AMDGPUUsage.html, gfx1030 是指 RX6800XT 以上的顯卡

而 RX6600 的 code name 為 gfx1032

$ ocminfo | grep gfx
  Name:                    gfx1032                            
      Name:                    amdgcn-amd-amdhsa--gfx1032

先這樣使用一段時間看有沒有問題,跑起來比純用 CPU 實在快太多了

另外發現 CPU 1 Core 吃滿的,但 GPU 耗電上限 100W 只吃 66W,可能是遇到 CPU 瓶頸了...,老舊的 2700x 該升級了嗎

星期日, 11月 26, 2023

Rust - String

String

Rust 中 String 的標準類型是 String

編碼是 UTF-8,這個特性給其他語言轉換過來的使用者比較疑惑的性質。

  • 無法使用 [] 取得字元。
  • .len() 取得的是 Bytes 長度,而非字數。
無法使用 [] 取得字元。
let mut st = String::from("您好,Rust");
println!("{}", st[0]);
會得到編譯錯誤
`String` cannot be indexed by `{integer}`
Rust 不支援 [] 的原因
  • Rust 中 String 是以 byte 為單位儲存
  • UTF-8 每個字不定長度
  • 計算字必須由第一個 Bytes 開始解析,時間複雜度為 O(n),而非 O(1)
那如何取得字數與特定位置的字呢?
let hello = "您好";
    
println("The second character is {}", hello.chars().nth(1)); // Get 好
println("The length of string is {}", hello.chars().count()); // Get 2
.chars() 可以取得對應字串的 iterator 來進行 parse 的動作。 但 Rust 不支援 grapheme
let hello = "Z̫̫̳̰̦͙͙̒̇̽̒͗̑̂̋̚ạ͈̳̟̙͉̥̦̙̒̔̃͋̅͛̐l͕̯̟͖̣͊̓̒̂̑̽̊̃͑̃̓̽ḡ̟̲͚̞͆͊̋̏̽̏̉ͅô̤͈̩̖̞̙̲͚͍͓̘͉͂́̆";
    
println("The second character is {}", hello.chars().nth(0)); // Get Z
println("The length of string is {}", hello.chars().count()); // Get 82
Rust 中的 char 為了能夠表示 Unicode 字元。其大小為 4 bytes
let c = '您'; // 
println!("{}", get_type_size(&c)); // get 4
每次 .chars 都會重新 Parse,想要保留結果可以將 String 轉為 Vec
let re = "您好, Rust。這是一個嘗試";
let re_v: Vec<char> = re.chars().collect();
print!("{}{}\n", re_v[0], re_v[1]); // Get 您好

String 與 str

在 Rust 中 Function 常常可以看到以下寫法
fn first_word(s: &str) -> &str {
}
&str
  • 有 & 所以是一個 Reference
  • str 是 String Slice, 相比一般 Reference 多了 "長度" 的資料
以下得範例
let p_i32: i32 = 0;
let p_String: String = format!("Hello world");

let r_i32 = &p_i32;
let r_String = &p_String;
let r_slice = &p_String[..]; // Full Size String Slice
型別 大小
&p_i32 &i32 8
&p_String &String 8
&p_String[..] &str 16
&str 相比 &String 多出的 8 bytes 就是紀錄長度需要的大小。

星期六, 11月 18, 2023

Rust 的基礎 - Option

在日常的程式開發中,有一種意外中止的情況:對 Null Pointer 進行了 Dereference。在 Java 中會丟出 Null Pointer Exception,在 C/C++ 則是觸發 Segmentation Fault。

那麼在 Rust 當中呢?

我們來看以下的範例

fn get_bigger<'a>(s1: &'a String, s2: &'a String) -> Option<&'a String> {
   if s1.len() > s2.len() {
      return Some(s1);
   } else if s1.len() < s2.len() {
      return Some(s2);
   } else {
      return None;
   }  
}
在這個範例中
  1. 定義了一個 get_bigger function
  2. 接受兩個 Reference
  3. 回傳 Option<&String>
    • 回傳較長字串的 Reference
    • 若一樣長,回傳 None

如果想要取得較長字串的長度

fn main() {
   let buf1 = "Hello".to_string();
   let buf2 = "World".to_string();

   let bigger = get_bigger(&buf1, &buf2);

   println!("{}", bigger.len());
}
會得到編譯錯誤
println!("{}", bigger.len());
                      ^^ method not found in `Option<&String>`
如果想要取得回傳的長度,必須對 Option 作拆封的動作
   if let Some(value) = bigger {
      println!("The bigger {}", value);
   } else {
      println!("Same");
   }

在使用 Option 的時候由於強制拆封,以此避免了對 None 進行操作的可能性。在以後的 Code 我們陸續可以看到這種強制拆封避免了開發者對不正常的值作操作的可能。

以下是完整的 Code

fn get_bigger<'a>(s1: &'a String, s2: &'a String) -> Option<&'a String> {
   if s1.len() > s2.len() {
      return Some(s1);
   } else if s1.len() < s2.len() {
      return Some(s2);
   } else {
      return None;
   }  
}

fn main() {
   let buf1 = "Hello".to_string();
   let buf2 = "World".to_string();

   let bigger = get_bigger(&buf1, &buf2);

   if let Some(value) = bigger {
      println!("The bigger {}", value);
   } else {
      println!("Same");
   }
}

星期六, 11月 11, 2023

Rust 的基礎 - Borrow

Rust 的基礎 - Reference 中,透過 reference 來存取到了 value。

Rust 的基礎- 所有權規則 中,有提到每個 Value 都有一個所有者 (owner)

在 Rust 中把創建一個 reference 的動作稱為借用 (borrowing)

  let hello = "hello".to_string();
  let hello_ref = &hello; // 借用 hello

為了確保 Reference 的安全,Rust 有一項編譯期檢查機制 "Borrow Checker",其用意是確保兩方面的事情

  • 所有者與 Reference 之間
  • Reference 與 Reference 之間
所有者與 Reference 之間

要確認的事項是 Rerference 不可以活得比 Owner 久,比如以下 code

   let hello = "hello".to_string();
   let hello_ref = &hello;

   drop(hello);
   println!("{}", hello_ref);

我們會得到以下錯誤

   let hello = "hello".to_string();
   let hello_ref = &hello;
                   ------ borrow of `hello` occurs here
   drop(hello);
        ^^^^^ move out of `hello` occurs here
   println!("{}", hello_ref);
                  -------- borrow later used here
Reference 與 Reference 之間

在說明 Reference 與 Reference 之間,首先先介紹 Mutable Borrowing,在介紹 Mutable Borrowing 前要先介紹什麼是 Mutable

Rust 相比其他常見的程式語言有一個特點,就是所有變數預設都是唯獨的

底下的這段 code
   let hello = "hello".to_string();
   hello = "world".to_string();
會得到編譯錯誤
   let hello = "hello".to_string();
   hello = "world".to_string();
   ^^^^^ cannot assign twice to immutable variable
想要編譯成功需要將程式改為
   let mut hello = "hello".to_string();
   hello = "world".to_string();

這麼作的思路是預設不能改可以避免潛在問題

而 Borrowing 所創建的 reference 預設是無法修改指向的 Value 的

   let mut hello = "hello".to_string();

   let hello_ref = &hello;

   hello_ref.push_str(" world");

會得到編譯錯誤

   let mut hello = "hello".to_string();

   let hello_ref = &hello;

   hello_ref.push_str(" world");
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `hello_ref` is a `&` reference, \
                                 so the data it refers to cannot be borrowed as mutable

想要修正這個錯誤,需要使用 mutable borrow

   let mut hello = "hello".to_string();

   let hello_ref = &mut hello;

   hello_ref.push_str(" world");

知道了什麼叫做 mutable borrowing 後,我們回到 Reference 與 Reference 之間 的情況,Rust 的規則是

  • borrowing 與 mutable borrowing 的 reference 不可重疊使用
  • 同時間只能有一個 mutable borrwing 的 reference

比如以下 code

   let mut hello = "hello".to_string();

   let hello_mut_ref = &mut hello;
   let hello_ref = &hello;

   hello_mut_ref.push_str(" world");
會得到編譯錯誤
   let mut hello = "hello".to_string();

   let hello_mut_ref = &mut hello;
                       ---------- mutable borrow occurs here
   let hello_ref = &hello;
                   ^^^^^^ immutable borrow occurs here

   hello_mut_ref.push_str(" world");
   -------------------------------- mutable borrow later used here

為什麼需要這種規則,可以看以下的 code

   let hello_mut_ref = &mut hello;
   let hello_ref = &hello;

   for c in hello_ref.chars() {
      hello_mut_ref.push_str(" world");
      // push_str may makes the iterator from hello_ref.chars() invalid.
      print!("{}", c);
   }

在這個範例中我們看到 hello_ref 去透過 chars function 取得了 hello 的 iterator,而在取用的途中,可以看到又透過 hello_mut_ref 在往 hello 內去附加字串。這種附加動作有可能會導致記憶體重分配,導致 hello 的 iterator 在途中變成無用的對象。

總結: Rust 的 Borrow Checker
  • 所有者與 Reference 之間
    • Reference 不可以活得比所有者久
  • Reference 與 Reference 之間
    • mutable borrowing 與 borrowing 不可同時存在
    • mutable borrowing 同時只能存在一個

以上規則可以有效防止 Dangling Pointer 的出現

星期六, 11月 04, 2023

Rust 的基礎 - Reference

在之前的所有權章節有提到以下的 code 行為是 move 而非複製

let hello = "Hello World".to_string(); let hello2 = hello; // hello move to hello2

在 Rust 中可以定義 function

fn add(v1: i32, v2: i32) -> i32{
  return v1 + v2;
}
在 move 的規則之下,在需要保留原本傳入參數的情況下。就得將原本的參數再回傳回來
fn concat(s1: String, s2: String) -> (String, String, String) {
   let result = format!("{}{}", s1, s2);

   return (s1, s2, result);
}

fn main() {
   let hello = "Hello".to_string();
   let world = "World".to_string();

   let (hello, world, hello_world) = concat(hello, world);

   println!("{} = {} + {}", hello_world, hello, world);
}

這種作法顯然會帶來兩個問題

  1. 撰寫麻煩
  2. 額外的 Stack 複製成本

為此 Rust 中引入了 reference 機制,以下為使用 Reference 的機制改寫的 code

fn concat(s1: &String, s2: &String) -> String {
   return format!("{}{}", s1, s2);
}

fn main() {
   let hello = "Hello".to_string();
   let world = "World".to_string();

   let hello_world = concat(&hello, &world);

   println!("{} = {} + {}", hello_world, hello, world);
}

在上述的範例中,主要做了兩項改變

  1. concat 參數的型別由String變為&String
  2. concat 傳入的變數由hello變為&hello

&String翻譯為中文的話是 "一個 Reference,指向的型別是 String"

&hello的話,& 則是一個運算子,對 hello 運算後會得到指向 hello 的 Reference

以下的 code 我們可以得到 String 與 &String 在記憶體中的大小

fn main() {
   println!("{}", std::mem::size_of::<String>());
   println!("{}", std::mem::size_of::<&String>());
}

在 x64 環境下,String 大小是 24 bytes,&String 大小則是 8 bytes,而 x86 環境下則分別是 12 bytes 與 4 bytes

可以發現 reference 的大小規則與所在平台的記憶體空間的所需定位大小一致,基本上就是 C 的指標

一旦有了指標出現,就會想到一些安全問題,比如 Dangling Pointer等,在之後的文章會介紹 Rust 如何防止 Rerference 的問題

星期六, 10月 28, 2023

Rust 的基礎 - Copy, Clone

所有權規則 那篇中,談到了

 

let hello = "Hello World".to_string()

let hello2 = hello;

 

在 Ownership 規則之下, hello 所持有的 Value 被移轉給 hello2,因此 hello 之後無法被使用。

這種規則可以止諸如 Dangling pointer, Use After Free 等。但想阻止這些情況除了 Move,其實也可以選擇複製。


let hello2 = hello.clone();


使用 clone 的話,hello 與 hello2 就各自持有單獨的一份 "Hello World"


此外對於一些基礎型別(如整數,浮點數)來說,它們在使用 = 時,行為就不是 Move 而是 Clone


let value = 123;

let value2 = value;

println!("{} {}", value, value2);

// value, value2 都有效


在 Rust 中,支援 Clone 的型別就有 clone 可用,支援 Copy 的型別遇到 = 時就是使用 clone。

星期六, 10月 21, 2023

Rust 的基礎- 所有權規則

 Rust 中

  1. 每個 Value 都有 "所有者"
  2. 在任意時刻只會有一個 "所有者"
  3. 當所有者離開其所在範圍時,Value 會被 Drop

第一條規則

let hello = "Hello World".to_string()

此範例 "Hello World" 是 Value, 所有者是 hello

 

第二條規則

let hello2 = hello;

此範例中 hello 所擁有的 "Hello World" 被轉移(Move) 給 hello2.

此時若在使用 hello, 則會編譯錯誤

16 |    let hell2 = hello;
   |                ----- value moved here
17 |    println!("{}", hello);
   |                   ^^^^^ value borrowed here after move


第三條規則

let hello = "Hello World".to_string()
{
    let hello2 = hello;
    // <-- 在離開 Scope 後, "Hello World" String 就被 Drop 了
}

從以上的三條規則來看,這些規則可能可以防範

  1. Dangling Pointer (由於一次只有一個 Owner, 被 Move 就失效因此無法再存取)
  2. Use After Free (由於一次只有一個 Owner, 被 Move 就失效因此無法再存取)
  3. Memory Leak (Circular 會有狀況)

星期一, 10月 09, 2023

Dependency Track - SBOM 產生

使用 Dependency Track 時,最麻煩的莫過於產生軟體清單。好在存在一些可從套件管理系統產生清單的軟體。

Python(PIP)

pip install cyclonedx-bom
cyclonedx-py -r -i requirements.txt.lock  --format json -o cyclonedx.json
 
requirements.txt.lock 的部份內容是
 
awscli==1.29.62
azure-core==1.29.4
azure-storage-blob==12.18.2
botocore==1.31.62

將最終產生的檔案後上傳



NPM

npm install @cyclonedx/cyclonedx-npm

./node_modules/.bin/cyclonedx-npm ../Angular/package-lock.json  --output-file cyclonedx.json

package.json 內容只有

"@angular/cli": "^16.2.5"

將最終產生的檔案後上傳

 




星期日, 10月 08, 2023

Dependency Track

 目前一家軟體公司在開發應用系統時,一般都會使用到第三方軟體,可能是 Library 或者是 Application 等等。而隨著時間經過,這些用到的第三方軟體可能會有弱點被逐步發現,進而影響既有系統安全性。

在 OWASP 2021 top ten 之中,"A06:2021 – Vulnerable and Outdated Components" 就高居第六項安全風險。在其中也給出了如何避免風險的建議

  1. 移除不必要的依賴,功能,元件與檔案 
  2. 持續使用工具清點 Client 端與 Server 端使用到的元件與其依賴的元件
  3. 持續使用使用自動化工具,以 CVE 與 NVD 來監控元件的弱點,以 Email 來收取安全通知警告
  4. 只從官方來源獲取資源,並只使用簽署過的 Packages
  5. 持續監控沒有維護或沒有為舊版本創建安全修正的 Library 或元件

在檢查是否使用到了有問題的 Library 或元件,很多語言的套件管理工具都有提供掃描功能:比如 npm, Composer, Cargo 等等,但對 C/C++ 雖然有 conan 這個套件管理系統,但由於非標準,組織未必有使用或者需要的 Package 並未收錄其中。 

在找尋追蹤方案的過程中,我發現了 depedency track 這個解決方案,其是這樣介紹自己的

Continuous SBOM Analysis Platform

SBOM 是 "Software Bill of Materials" 的縮寫,代表開發時使用到的元件。

想使用這各平台,可以簡單的透過 Docker Composer 來使用

curl -LO https://dependencytrack.org/docker-compose.yml
docker-compose up -d

啟動後的可以透過 http://127.0.0.1:8080 來存取,預設的帳號密碼是 admin/admin

以下是我創建的一個範例用的專案


 

 

 

 

點開 curl 在 vulnerabilities 中可以看到該版本的弱點


 



 

 

 

為了要讓其可以抓到對應的版本,我目前是在 CPE 中填入

 

 

 

 

 

 

 

 

 

 

目前實測軟體版本比對有時可能會有問題,有可能會比到上古版本的安全問題


 

安全資料主要來自 NVD,會自動更新


 





當然也有通知功能


 



 

 

 

 

其也有提供 OpenAPI 界面來幫助與現有的開發流程進行整合


 

 

 

 

 

 

日後有其他使用上的心得會更新上來。





星期三, 9月 27, 2023

Rust 初接觸

近日以來,Rust 逐步擴展到一些系統底層,比如 Microsoft 正在將 Win32 GDI 以 Rust 進行重寫(出處),Linux Kernel 6.1 也開始引入 Rust 的支援。能被著名的 C++ 反對者 Linus 接受成為 Linux 第二開發語言(僅用於 Module),Rust有什麼優勢呢?

[PATCH 00/13] [RFC] Rust support 中說明了在 Linux 中引入 Rust 希望達成以下目標

  1. 以 Rust 撰寫有助於減少 memory safety bugs,data races 與邏輯錯誤
  2. 維護者能更有信心去重構或接受模組相關的 patch
  3. 得益於抽象化,現代語言特性與詳細的文件,模組撰寫更加容易
  4. 使用現代語言更有助於吸引更多人加入
  5. 透過 Rust 工具,持續執行至今的文件指南

也說明了在 Linux 開發中,Rust 比 C 好的部份

  1. 無未定義行為, 包括 memory safety 與 data races
  2. 更嚴格的型別系統減少了邏輯錯誤
  3. 安全的 Code 與 "unsafe" code 更容易區別出來
  4. 豐富的語言特性
  5. 廣泛的獨立 Library
  6. 整合了以編譯器為基底的文件產生,formatter 與 linter

在 Google Security Blog 中, Rust in the Android platform 中則說明了幾件事情

  1. 即使付出了很多努力,在高危險漏洞中 memory safety 仍長佔據約 70% 的比例。
  2. 記憶體安全的語言有很多,但不適合用於需要精確控制與可預期行的底層系統開發。
  3. Rust 透過了編譯時檢查與執行時檢查達到了與 C/C++ 同等的性能。

在微軟的安全回應中心的 Blog,Why Rust for safe systems programming 當中提出了當下 Rust 是 C/C++ 的最合適替代品。

  1. 想達成記憶體安全又可進行底層系統開發
  2. 與 C/C++ 效能相當且能進行細緻控制
  3. 在高危險漏洞中,70% 是記憶體安全問題。使用 Rust 可以避免
    1. 編譯期與執行期皆能處理
    2. 不安全的記憶體操作需明確標示,大幅減少人工檢查的範圍
  4. 除此之外
    1. Rust 只要編譯通過,就能工作
    2. 除了記憶體安全,也避免 Null Pointer 與 data races
    3. 豐富的型別系統有助於撰寫有表達力的程式,在 enum 與 trait 可以進一步幫助盡可能沒有 Bug 的程式
    4. 有強大的社群

總結來看,記憶體安全,執行效率與細緻控制是該語言的強項。

日後可能會不定時更新一些學習的內容。