网页设计和网站编辑,wordpress 页面瀑布流,wordpress登陆不跳转,付费小说网站建设用户向程序输入数据#xff0c;程序分析数据#xff0c;但是当用户的输入大于缓冲区长度时#xff0c;数据会溢出#xff0c;覆盖掉内存中其他内容#xff0c;比如函数返回地址#xff0c;从而可能导致程序返回到错误的地址执行了不安全的程序#xff08;远程代码执行程序分析数据但是当用户的输入大于缓冲区长度时数据会溢出覆盖掉内存中其他内容比如函数返回地址从而可能导致程序返回到错误的地址执行了不安全的程序远程代码执行——蠕虫病毒
类型错误有符号变成无符号而没有标识
动态分析只有在发生了才能检测到错误
Valgrind检测内存错误依赖输入通过检测只代表当前输入下没问题
模糊检测Valgrind和其他程序结合产生大量输入测试
静态分析不运行代码判断是否会停机、退出或出错 Rust的最终目的是进行一些静态分析从而检测安全问题 Program Analysis
Valgrind的作用是实时修补程序编译产生的汇编代码在Valgrind下运行这些汇编代码时调用malloc之类内存分配函数时会被替换成Valgrind内部的内存分配函数以做到观察内存情况。可以在没有源代码的情况下分析程序
sanitizer在源码中插入代码进行检测可以获得更多的信息
模糊检测分为盲目模糊检测和基于覆盖率的模糊检测
基于覆盖率的模糊检测通过不断地执行代码画控制流图
linting通过找到可能导致重大后果的明显错误来检测代码如用户输入时调用的gets函数代码检测器的持续反馈
数据流分析识别数据在可能的执行路径中的情况常用于检测变量是否初始化和是否释放内存
活性分析检测程序某一点的变量是否拥有内存并持续阅读源代码超过作用域前是否还没释放内存 Memory Safety
所有权哪一部分代码负责分配内存哪一部分代码负责释放该内存这样就能将统一类内存统一管理而不会造成内存泄漏
前置条件和后置条件如内存由谁来分配前置条件分配后使用使用后由谁来释放后置条件
使用更好的类型系统来实际制定我们在代码中的意图前置条件和后置条件以便编译器能理解从而在编译过程中检查是否满足——Rust
# rust命令行编译
rustc xxx.rs
# cargo创建项目
cargo new xxx
# cargo构建项目
cargo build
# cargo编译执行
cargo runfn main(){let julio hello.to_string();//julio有字符串的所有权let ryan julio;//所有权转移到ryan上julio失去所有权println!({}, ryan);//正常println!({}, julio);//错误:error[E0382]: borrow of moved value: julio
}Rust中的值只有一个所有者每个值当到达作用域边界时就根据所有者释放它
借用类似于引用、起别名但不会改变所有权归属
fn main(){let julio hello.to_string();//julio有字符串的所有权let ryan julio;//所有权还在julio上ryan借用println!({}, ryan);//正常println!({}, julio);//正常
}Error Handling
Rust在编译期使用特有的所有权模型自动调用free而到了执行期与c相同所以没有性能下降
fn main(){let s String::from(hello);//不可变常量s.push_str( world);//错误error[E0596]: cannot borrow s as mutable, as it is not declared as mutable
}fn main(){let mut s String::from(hello);//mut关键字可变s.push_str( world);//正常
}fn func(s: String){println!({}, s);
}fn main(){let s String::from(hello);func(s);//将字符串的所有权交给func函数s不再有所有权func(s);//此时s没有所有权错误通过传递引用解决
}Rust强制你清晰地表达你的意思从而使编译器捕获之前无法捕获的错误
可变引用和不可变引用 Rust的引用规则为 在作用域中的数据有且只能有一个可变引用可以有多个不可变引用不能同时拥有不可变引用和可变引用。 //println!不是简单的读数据更类似使用数据
fn main(){let s String::from(hello);let s1 mut s;//可变引用相当于一个写线程错误因为不能从一个不可变常量中获取可变引用let s2 s;//不可变引用相当于一个读线程println!({} {} {}, s, s1, s2);
}fn main(){let mut s String::from(hello);let s1 mut s;//可变引用相当于一个写线程println!({} {}, s, s1);//错误因为不能同时通过同一个值的两个可以修改值的量来使用值
}fn main(){let mut s String::from(hello);let s1 mut s;println!({}, s);//错误使用值之后s1会使用但是可能导致s使用失效身为所有者println!({}, s1);
}fn main(){let mut s String::from(hello);let s1 mut s;println!({}, s1);//正确因为在定义s1和使用s1间s没有使用值所以s1直接使用值是安全的println!({}, s);//正确s1使用完了s才使用线程安全
}
拒绝服务攻击如内存不够或分配失败memcpy拷贝到内存非法的地方
空值给程序员带来了巨大的负担需要不断跟踪如果有空值可能是因为资源分配问题或是仅仅表示一种状态
Rust通过引入Option类型来解决空值的含义模糊问题
fn option() - OptionString{if random_num() 10{Some(String::from(bigger than 10))}else{None}
}fn main(){if option().is_none(){println!(less than 10);}
}fn main(){let message option().unwrap_or(String::from(less than 10));//message为some或为给出的默认值
}fn main(){match option(){Some(message) {println!(get message:{}, message);},None {println!(dont get message);}}
}Object Oriented Rust Rust中的几乎所有内容都是一个表达式。 表达式是返回值的东西。 如果输入分号则会抑制该表达式的结果在大多数情况下这就是您想要的结果。 另一方面这意味着如果您使用不带分号的表达式结束函数则将返回最后一个表达式的结果。 match 语句中的块也可以使用相同的内容。 您可以在其他任何需要值的地方使用不带分号的表达式。 自动解引用
fn main(){let x: Boxu32 Box::new(10);println!({}, *x);//输出10
}链表结构体
struct Node{value: u32,next: OptionBoxNode,//Box是指向堆上对象的指针使下一个Node在堆上分配从而保证指向的Node能在需要的时候存在而Option的None表示结束链表
}struct LinkList{head: OptionBoxNode,size: usize,
}//实现Node
impl Node{//公共函数pub fn new(value: u32, next: OptionBoxNode) - Node{//构建NodeNode{value: value, next: next}//不加分号返回构建的Node}
}//实现链表
impl LinkList{pub fn new() - LinkList{LinkList{head: None, size: 0}}pub fn get_size(self) - usize{//(*self).size//显式解引用指针self.size}pub fn is_empty(self) - bool{self.get_size() 0}pub fn push(mut self, value: u32){let node: BoxNode Box::new(Node::new(value, self.head.take())); //take()从中取出值在其位置留下None self.head Some(node);//头插法self.size 1;}pub fn pop(mut self) - Optionu32{let node :BoxNode self.head.take()?;//函数返回Option如果右式为None就直接返回Noneself.head node.next;self.size - 1;Some(node.value)}
}fn main(){let mut list: LinkList LinkList::new();assert!(list.is_empty());assert_eq!(list.get_size(), 0);for i in 1...10{//[1, 10)list.push(i);}println!({}, list.pop().unwrap());
}Traits and Generics
Traits用于显示、拷贝、迭代器、比较等
//覆盖默认Drop
impl Drop for LinkList{//链表的Drop特征当链表超过作用域时调用删除fn drop(mut self){let mut current self.head.take();while let Some(mut node) current{//覆盖了原来的前结点此时前结点无所有者释放current node.next.take();//拿走下一个}}
}print!({:?}, x);//输出x的调试版本#[derive()] 是 Rust 编程语言中的一个属性attribute用于自动为结构体struct或枚举enum实现特定的 traits特性。Traits 定义了类型可以实现的行为使用 #[derive()] 属性可以让编译器为我们生成实现这些 traits 所需的代码。 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Point {x: f64,y: f64
}impl Point{pub fn new(x: f64, y: f64){Point{x: x, y: y}}
}fn main() {let origin Point::new(0.0, 0.0);let mut p origin;// copy语义产生一个新的Pointprint!({:?}, p);//Debug}PartialEq 和 Eq这两个 Traits 的名称实际上来自于抽象代数中的等价关系和局部等价关系实际上两者的区别仅有一点即是否在相等比较中是否满足反身性Reflexivity。 实现 Eq 的前提是已经实现了 PartialEq因为实现 Eq 不需要额外的代码只需要在实现了PartialEq 的基础上告诉编译器它的比较满足自反性就可以了。 自定义train
#[derive(Debug, Clone, PartialEq)]
struct Point {x: f64,y: f64
}impl Point{//new需要指定- Self它只表示当前类型Pointpub fn new(x: f64, y: f64) - Self{Point{x: x, y: y}}
}pub trait ComputeNorm{//默认实现fn compute_norm(self) - f64 {0.0//默认返回0.0}
}//Point实现的ComputeNorm
impl ComputeNorm for Point {fn compute_norm(self) - f64 {(self.x * self.x self.y * self.y).sqrt()}
}impl ComputeNorm for Optionf64 {}//使用默认实现fn main() {let some_point Point::new(3.0, 4.0);print!({}, some_point.compute_norm());//输出5
}impl Add for Point {type Output Self;//关联类型这里说明输出的类型和自身类型相同fn add(self, other: Self) - Self {Point::new(self.x other.x, self.y other.y)}
}泛型
pub enum MyOptionT {SUNTHIN(T),MUTHIN
}
pub struct MatchingPairT {first: T,second: T
}implT MatchingPairT {pub fn new(first: T, second: T) - Self {MatchingPair {first: first, second: second}}
}//实现MatchingPair的Display当泛型T实现了fmt的Display方法约束
impl fmt::Display for MatchingPairT where T: fmt::Display{fn fmt(self, f: mut fmt::Formatter_) - fmt::Result {write!(f, ({}, {}) self.first, self.second)//可以自由地写泛型类型的数据而不用单独实现}
}Smart Pointers
implT: Clone Clone for MatchingPairT {fn clone(self) - Self {MatchingPair::new(self.first.clone(), self.second.clone())}
}trait组合
//保证既能比较又能打印
fn print_minT: fmt::Display ParialOrd(x: T, y: T) {if x y {println!({}, x);} else {println!({}, y);}
}Rc T 引用计数指针只能处理不可改变的堆内存块。允许多个不可变引用可能导致循环引用问题 use std::rc::Rc;struct LinkedList {head: OptionRcNode,size: usize
}impl LinkedList {pub fn push_front(self, value: u32) - Self {let new_node Rc::new(Node::new(value, self.head.clone());LinkedList {head: Some(new_node), size: self.size 1}}
}
fn main() {let list: LinkedList LinkedList::new();let version1 list.push_front(10);let version2 version1.push_front(5);let version3 version2.push_front(3);println!({}, version1);//10println!({}, version2);//10 5println!({}, version2);//10 5 3
}RefCell T 内部可变性可变地借用一个不可变的值允许我们在只持有不可变引用的前提下对数据进行修改。 借用检查的工作仅仅是被延后到了运行阶段如果违反了借用规则还是会触发panic!更高的运行成本 常用形式 Rc RefCell T 用于指向同一数据块的多个指针如双向链表 Pitfalls in Multiprocessing
多线程僵尸线程即未被回收的线程
int func_that_might_fail(){pid_t pid1 fork();if (pid1 0) {printf(1);return 0;}
}int main(){func_that_might_fail();pid_t pid2 fork();if (pid2 0) {printf(2);return 0;}
}命令可以理解成就是一个子进程该子进程执行一个命令
let output Command::new(ps).args([--pid, pid.to_string(), -o, pid ppid command]).output()//输出到缓冲区父进程阻塞等待子进程完成并返回内容用于调用外部二进制文件并等待执行完成.expect(Failed to execute subprocess)//执行子进程let status Command::new(ps).args([--pid, pid.to_string(), -o, pid ppid command]).status()//状态同output不过不改变子进程标准输出位置父进程是啥它就是啥返回退出状态码.expect(Failed to execute subprocess)//执行子进程let child Command::new(ps).args([--pid, pid.to_string(), -o, pid ppid command]).spawn()//产生分支子进程返回子进程的句柄可用来让子进程等待let status child.wait().expect(Failed to execute subprocess)//执行子进程预执行函数在子进程创建后和执行前由父进程执行的函数
use std::os::unix::process::CommandExt;let cmd Command::new(ls);//创建
unsafe {cmd.pre_exec(function_to_run);//执行预执行函数
}
let child cmd.spawn();//子函数执行应安排一个新管道来连接父子进程
let mut child Command::new(cat).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
child.stdin.as_mut().unwrap().write_all(bHello, world!)?;
let output child.wait_with_output()?;Pitfalls in Signal Handling 使用sigaction.sa_mask可以保证处理信号时又来的信号阻塞父进程执行waitpid等待所有子进程执行完才使用sigchld_count打印安全 子进程进行waitpid等待会导致死锁 printf会使用锁flock()将标准输出锁住当你调用printf且已经获得了锁的时候来了个信号你转去处理信号handler调用printf此时再flock()会发现输出已经被锁住了等待死锁
不在处理程序中完成工作而是在拦截信号中完成通过设置全局变量来指定handler是否完成工作从而来让main完成
自管道和自己通信的管道main只需要不停读管道而handler负责将信号写入管道但是无法同时读/写管道。使用线程或非阻塞IO
Rest没有内置信号处理需要依赖外部库ctrlc crate它使用了自管道和多线程
多线程进行信号处理打印是安全的因为在handler中打印被阻塞时main不会停住而是继续打印然后释放锁handler打印
线程和信号处理程序具有相同的并发性问题但是代码的调度是完全不同的
线程多个(通常)等优先级的执行线程在处理器上不断交换可以使用锁来保护数据信号处理程序处理程序将完全抢占所有其他代码并占用CPU直到它完成不能使用锁或任何其他同步原语实际上信号处理程序应该避免各种阻塞!(为什么?)
因此信号处理程序对库代码比如printf的处理非常糟糕。库不知道您安装了什么信号处理程序也不知道这些信号处理程序做什么因此它们不能禁用信号处理来保护自己免受并发问题的影响
多进程vs多线程
速度多线程免于切换上下文速度更快内存使用多线程共享内存使用率更高CPU使用多线程更好同理易用性多线程不需要考虑通信等问题更好安全性多进程更好一个Bug破坏了内存共享内存会最终导致多线程崩溃而多进程只会让单个进程崩溃
这也就是为什么Google Chrome选择多进程架构因为Google认为无法解决所有问题因此用进程隔离开防止全部崩溃
Mozilla认为是可以解决大部分问题的所以为Firefox创建了Rust尝试解决问题 Intro to Multithreading
一个标签页就是一个进程
竟态条件软件的条件和行为取决于无法控制的事件的顺序或时间你能从不同的事件的事件安排中得到不同的结果
fn main() {let mut threads Vec::new();for _ in 0..NUM_THREADS {threads.push(thread::spawn(|| {//闭包函数/lambda函数 有参时|x: u32, f: f64|let mut rng rand::thread_rnp();thread::sleep(time::Duration::from_millis(rng.gen_range(0, 5000)));println!(thread finished running!);}));}for handle in threads {handle.join().expect(panic happend inside of a thread!);}println!(all threads finished!);
}Shared Memory 当线程之间所有权需要共享时可以使用Arc共享引用计数Atomic Reference Counted 缩写。这个结构通过 Arc::clone(x) 实现可以为内存堆中的值的位置创建一个引用指针同时增加引用计数器。由于它在线程之间共享所有权因此当指向某个值的最后一个引用指针退出作用域时该变量将被删除。Arc是Rc的多线程版本 move
const NAMES: [self; 6] [frank, jon, lauren, marco, julie, patty]
fn main() {let mut threads Vec::new();for i in 0..6 {threads.push(thread::spawn(move || {//move将所引用的所有变量i的所有权转移至闭包内通常用于使闭包的生命周期大于所捕获的变量的原生命周期拷贝println!({}, NAMES[i]);}));}for handle in threads {handle.join().expect(panic occurred in thread);}
}使用Rc RefCell 作为共享内存可以让各个线程都操作一个变量
fn main() {let remainingTickets: RcRefCellusize Rc::new(RefCell::new(250));let mut threads Vec::new();for i in 0..10 {let remainingTicketRef remainingTickets.clone();threads.push(thread::spawn(|| {ticketAgent(i, mut remainingTicketRef.borrow_mut())}));}for handle in threads {handle.join().expect(panic occurred in thread);}
}此时Rc是不安全的因为可能有多个线程同时修改同一变量使用Arc保证操作的原子性并且要加锁
Mutex本质上时一个存储一段内存的容器确保你可以拥有独占的访问权限但是不能拷贝所以外包一层Arc实现拷贝 fn ticketAgent(id: usize, remainingTickets: ArcMutexusize) {loop {let mut remainingTicketsRef remainingTickets.lock().unwrap();...}
}fn main() {let remainingTickets: ArcMutexusize Arc::new(Mutex::new(250));let mut threads Vec::new();for i in 0..10 {let remainingTicketRef remainingTickets.clone();threads.push(thread::spawn(move || {ticketAgent(i, remainingTicketRef)//即使克隆remainingTicketRef也是指向同一内存的}));}for handle in threads {handle.join().expect(panic occurred in thread);}
}互斥锁持续在ticketAgent的循环作用域中存在直到离开循环此时互斥锁释放并释放它锁住的东西 Synchronization
条件变量Condvar和互斥锁相关互斥锁和数据片段相关 解决资源访问顺序的问题。而 Rust 考虑到了这一点为我们提供了条件变量(Condition Variables)它经常和Mutex一起使用可以让线程挂起直到某个条件发生后再继续执行 将条件变量与互斥量关联起来的习惯做法是将它们成对放在一起并将这对变量包装在Arc中。
实现多线程通过队列和主线程通信队列只有一个主线程不断等待子线程传入队列的信息
# Cargo.toml
[dependencies]
rand 0.7.3 # 使用的rand库版本extern crate rand;
use std::sync::{Arc, Mutex, Condvar};
use std::{thread, time};
use std::collections::VecDeque;
use rand::Rng;fn rand_sleep() {let mut rng rand::thread_rng();thread::sleep(time::Duration::from_millis(rng.gen_range(0, 30)));
}#[derive(Clone)]//Arc实现了Clone所以不需要单独实现它的Clone
pub struct SemaPlusPlusT {//Pair使用.0和.1访问元素queue_and_cv: Arc(MutexVecDequeT, Condvar),
}implT SemaPlusPlusT {pub fn new() - Self {SemaPlusPlus {queue_and_cv: Arc::new((Mutex::new(VecDeque::new()), Condvar::new()))}}//发送pub fn send(self, message: T) {// 获得锁let mut queue self.queue_and_cv.0.lock().unwrap();queue.push_back(message);// 只在队列变成非空时才通知全体线程if queue.len() 1 {self.queue_and_cv.1.notify_all();}}//接收pub fn recv(self) - T {let mut queue self.queue_and_cv.0.lock().unwrap()queue self.queue_and_cv.1.wait_while(queue, |queue| {queue.is_empty()//队列为空就一直等待}).unwrap();// 将锁移动到等待状态等待结束后返回锁queue.pop_front().unwrap()}
}fn main() {let sem: SemaPlusPlusString SemaPlusPlus::new();let mut handles Vec::new();for i in 0..12 {// 每个线程克隆一个但都指向同一SemaPlusPluslet sem_clone sem.clone();let handle thread::spawn(move || {rand_sleep();sem_clone.send(format!(thread {} finished, i))});handles.push(handle);}for _ in 0..12 {println!({}, sem.recv());}for handle in handles {handle.join().unwrap();}
}C/C没有将锁和数据联系起来而Rust要求必须获得锁才能使用数据 Channels
Golang通过通信共享内存而不是共享内存来通信多线程不共享内存
如果不共享内存则需要将数据复制到消息中开销会很大。我们共享一些内存在堆中并且只在通道中进行浅拷贝。
通道减少了数据竞争的可能性但如果使用不当因为会有多个指针指向同一内存也不能排除竞争
MPMC通道多生产者多消费者理想通道
Rust使用MPSC通道多生产者单消费者
use std::thread;
use crossbeam_channel::unbounded;let (sender, receiver) unbounded();// Computes the n-th Fibonacci number.
fn fib(n: i32) - i32 {if n 1 {n} else {fib(n - 1) fib(n - 2)}
}// Spawn an asynchronous computation.
thread::spawn(move || sender.send(fib(20)).unwrap());// Print the result of the computation.
println!({}, receiver.recv().unwrap());使用通道比使用锁和CV更加安全但是在极端情况下还是会导致问题
通道并不总是最好的选择不太适合全局值(例如缓存或全局计数器) Scalability and Availability
可扩展性和可用性两者此消彼长
Netflix通过在一个可控的环境中故意诱导失败来检查复杂系统中可能出现的故障混沌工程——“对系统进行实验的学科以建立对系统在生产中承受动荡条件的能力的信心。”Simian Army——Chaos Monkey Information Security
安全的两个原则
身份验证Authentication你是谁授权Authorization你正在做的是否是你被允许做的 作业笔记
/proc/{pid}/fd中存放进程所用的文件描述符的符号链接指向文件描述符在vnode表中指向的任何文件可以从/proc/{pid}/fdinfo/{fd}获取关于每个文件描述符的额外信息
获取文件
let dir fs::read_dir(format!(/proc/{}/fd, self.pid)).ok()?;
for entry in dir{let name entry.expect(process error).file_name();let parsed: usize name.into_string().expect(osstring to string error).parse().unwrap();result.push(parsed);
}创建泛型结构体
struct NodeT {value: T,next: OptionBoxNodeT,
}implT NodeT {pub fn new(value: T, next: OptionBoxNodeT) - NodeT {Node::T {value: value, next: next}}
}nix::sys::ptrace::cont重新启动已停止的跟踪进程ptrace(PTRACE_CONT, ...)
使用 PID 继续执行进程可选 传递由sig指定的信号
pub fn contT: IntoOptionSignal(pid: Pid, sig: T) - Result()Child::kill()强制子进程退出。如果子项已退出则返回Ok(())
pub fn kill(mut self) -Result()nix::sys::ptrace::getregs()获取用户程序的寄存器
pub fn getregs(pid: Pid) - Resultuser_regs_struct为了有用backtrace应该显示函数名和行号以便程序员可以看到程序的哪些部分正在运行。然而正在运行的可执行文件仅由汇编指令组成不知道函数名或行号。为了打印这些信息我们需要读取存储在为调试而编译的可执行文件中的额外调试符号。这个调试信息存储地址和行号、函数、变量等之间的映射。有了这些信息我们就可以找到变量在内存中的存储位置或者根据处理器指令指针的值找出正在执行的是哪一行。
void
bar(int a, int b)
{int x, y;x 555;y ab;
}void
foo(void) {bar(111,222);
}堆栈帧由两个寄存器限定%rbp中的值是当前堆栈帧顶部的地址%rsp中的值是当前堆栈帧底部的地址(%ebp和%esp指的是寄存器的较低32位但您将分别处理%rbp和%rsp)。
读进程内存
rbp ptrace::read(self.pid(), (地址) as ptrace::AddressType)? as usize;如何在进程上设置断点?答案可能比您想象的要复杂但这正是GDB的工作原理。要在0x123456的指令上设置断点只需使用ptrace写入子进程的内存将0x123456的字节替换为值0xcc。这对应于INT(“中断”)指令;任何运行此指令的进程都将暂时停止。
当我们“遇到断点”时下级程序执行了0xcc INT指令导致下级程序暂停(由于SIGTRAP)。然而0xcc指令覆盖了程序中有效指令的第一个字节。如果我们从0xcc之后继续执行我们将跳过一条合法的指令。更糟糕的是许多指令有多个字节长。如果我们在多字节指令上设置断点并继续执行CPU将尝试将指令的第二个字节解释为一个新的独立指令。程序很可能会由于段错误或非法指令错误而崩溃。
为了从断点继续我们需要用原始指令的值替换0xcc。然后我们需要倒带指令指针(%rip)使其指向原始指令的开头(而不是指向其中的一个字节)。
这样做之后我们可以继续执行。但是我们的断点不再在代码中因为我们已经将0xcc换成了真正的指令。如果我们在循环或多次调用的函数中设置了断点这是不理想的!