CS110L 系统编程安全 笔记

用户向程序输入数据,程序分析数据,但是当用户的输入大于缓冲区长度时,数据会溢出,覆盖掉内存中其他内容,比如函数返回地址,从而可能导致程序返回到错误的地址执行了不安全的程序(远程代码执行)——蠕虫病毒

类型错误:有符号变成无符号而没有标识image-20231122132619982

动态分析:只有在发生了才能检测到错误

Valgrind检测内存错误:依赖输入,通过检测只代表当前输入下没问题

模糊检测:Valgrind和其他程序结合,产生大量输入测试

静态分析:不运行代码,判断是否会停机、退出或出错

image-20231122134317706

Rust的最终目的是进行一些静态分析,从而检测安全问题


Program Analysis

Valgrind的作用是实时修补程序编译产生的汇编代码,在Valgrind下运行这些汇编代码时,调用malloc之类内存分配函数时会被替换成Valgrind内部的内存分配函数,以做到观察内存情况。可以在没有源代码的情况下分析程序

sanitizer:在源码中插入代码进行检测,可以获得更多的信息

模糊检测:分为盲目模糊检测和基于覆盖率的模糊检测

基于覆盖率的模糊检测:通过不断地执行代码画控制流图image-20231122143832608

linting:通过找到可能导致重大后果的明显错误来检测代码,如用户输入时调用的gets函数,代码检测器的持续反馈

数据流分析:识别数据在可能的执行路径中的情况,常用于检测变量是否初始化和是否释放内存

活性分析:检测程序某一点的变量是否拥有内存,并持续阅读源代码,超过作用域前是否还没释放内存

image-20231122151652836


Memory Safety

所有权:哪一部分代码负责分配内存,哪一部分代码负责释放该内存,这样就能将统一类内存统一管理,而不会造成内存泄漏

前置条件和后置条件:如内存由谁来分配(前置条件),分配后使用,使用后由谁来释放(后置条件)

使用更好的类型系统来实际制定我们在代码中的意图(前置条件和后置条件),以便编译器能理解,从而在编译过程中检查是否满足——Rust

# rust命令行编译
rustc xxx.rs
# cargo创建项目
cargo new xxx
# cargo构建项目
cargo build
# cargo编译执行
cargo run
fn 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拷贝到内存非法的地方image-20231122200047207

空值给程序员带来了巨大的负担,需要不断跟踪,如果有空值可能是因为资源分配问题,或是仅仅表示一种状态

Rust通过引入Option类型来解决空值的含义模糊问题

fn option() -> Option<String>{
    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!("don't get message");
        }
    }
}

Object Oriented Rust

Rust中的几乎所有内容都是一个表达式。 表达式是返回值的东西。 如果输入分号,则会抑制该表达式的结果,在大多数情况下,这就是您想要的结果。 另一方面,这意味着如果您使用不带分号的表达式结束函数,则将返回最后一个表达式的结果。 match 语句中的块也可以使用相同的内容。 您可以在其他任何需要值的地方使用不带分号的表达式。

自动解引用:

fn main(){
	let x: Box<u32> = Box::new(10);
    println!("{}", *x);//输出10
}

链表结构体:

struct Node{
    value: u32,
    next: Option<Box<Node>>,//Box是指向堆上对象的指针,使下一个Node在堆上分配,从而保证指向的Node能在需要的时候存在,而Option的None表示结束链表
}

struct LinkList{
    head: Option<Box<Node>>,
    size: usize,
}

//实现Node
impl Node{
    //公共函数
    pub fn new(value: u32, next: Option<Box<Node>>) -> Node{
        //构建Node
        Node{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: Box<Node> = Box::new(Node::new(value, self.head.take())); //take()从中取出值,在其位置留下None 
        self.head = Some(node);//头插法
        self.size += 1;
    }
    
    pub fn pop(&mut self) -> Option<u32>{
        let node :Box<Node> = self.head.take()?;//函数返回Option,如果右式为None就直接返回None
        self.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语义,产生一个新的Point
    print!("{:?}", 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,它只表示当前类型(Point)
    pub 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 Option<f64> {}//使用默认实现

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 MyOption<T> {
    SUNTHIN(T),
    MUTHIN
}
pub struct MatchingPair<T> {
    first: T,
    second: T
}

impl<T> MatchingPair<T> {
    pub fn new(first: T, second: T) -> Self {
        MatchingPair {first: first, second: second}
    }
}

//实现MatchingPair的Display,当泛型T实现了fmt的Display方法(约束)
impl fmt::Display for MatchingPair<T> where T: fmt::Display{
    fn fmt(&self, f: &mut fmt::Formatter<`_>) -> fmt::Result {
        write!(f, "({}, {})" self.first, self.second)//可以自由地写泛型类型的数据而不用单独实现
    }
}

Smart Pointers

impl<T: Clone> Clone for MatchingPair<T> {
    fn clone(&self) -> Self {
        MatchingPair::new(self.first.clone(), self.second.clone())
    }
}

trait组合:

//保证既能比较又能打印
fn print_min<T: fmt::Display + ParialOrd>(x: T, y: T) {
    if x < y {
        println!("{}", x);
    } else {
        println!("{}", y);
    }
}

Rc< T >:引用计数指针,只能处理不可改变的堆内存块。允许多个不可变引用,可能导致循环引用问题

image-20231124150134142

use std::rc::Rc;

struct LinkedList {
    head: Option<Rc<Node>>,
    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);//10
    println!("{}", version2);//10 5
    println!("{}", 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(b"Hello, world!")?;
let output = child.wait_with_output()?;

Pitfalls in Signal Handling

image-20231125114522577

使用sigaction.sa_mask可以保证处理信号时又来的信号阻塞,父进程执行waitpid等待所有子进程执行完才使用sigchld_count打印,安全

image-20231125115652764

子进程进行waitpid等待会导致死锁

image-20231125120135592

printf会使用锁flock()将标准输出锁住,当你调用printf且已经获得了锁的时候,来了个信号,你转去处理信号,handler调用printf,此时再flock()会发现输出已经被锁住了,等待,死锁

不在处理程序中完成工作,而是在拦截信号中完成,通过设置全局变量来指定handler是否完成工作,从而来让main完成

自管道:和自己通信的管道,main只需要不停读管道,而handler负责将信号写入管道,但是无法同时读/写管道。使用线程或非阻塞IO

Rest没有内置信号处理,需要依赖外部库ctrlc crate,它使用了自管道和多线程

多线程进行信号处理打印是安全的:因为在handler中打印被阻塞时main不会停住,而是继续打印,然后释放锁,handler打印

线程和信号处理程序具有相同的并发性问题但是代码的调度是完全不同的

  • 线程:多个(通常)等优先级的执行线程在处理器上不断交换可以使用锁来保护数据
  • 信号处理程序:处理程序将完全抢占所有其他代码并占用CPU,直到它完成不能使用锁或任何其他同步原语实际上,信号处理程序应该避免各种阻塞!(为什么?)

因此,信号处理程序对库代码(比如printf)的处理非常糟糕。库不知道您安装了什么信号处理程序,也不知道这些信号处理程序做什么,因此它们不能禁用信号处理来保护自己免受并发问题的影响

多进程vs多线程:image-20231125131218581image-20231125131333690

  1. 速度:多线程免于切换上下文,速度更快
  2. 内存使用:多线程共享内存,使用率更高
  3. CPU使用:多线程更好,同理
  4. 易用性:多线程不需要考虑通信等问题,更好
  5. 安全性:多进程更好,一个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: Rc<RefCell<usize>> = 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实现拷贝

image-20231125183423454

fn ticketAgent(id: usize, remainingTickets: Arc<Mutex<usize>>) {
    loop {
        let mut remainingTicketsRef = remainingTickets.lock().unwrap();
        ...
    }
}

fn main() {
    let remainingTickets: Arc<Mutex<usize>> = 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 SemaPlusPlus<T> {
    //Pair,使用.0和.1访问元素
    queue_and_cv: Arc<(Mutex<VecDeque<T>>, Condvar)>,
}

impl<T> SemaPlusPlus<T> {
    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: SemaPlusPlus<String> = SemaPlusPlus::new();
    let mut handles = Vec::new();
    for i in 0..12 {
        // 每个线程克隆一个,但都指向同一SemaPlusPlus
        let 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 Node<T> {
    value: T,
    next: Option<Box<Node<T>>>,
}

impl<T> Node<T> {
    pub fn new(value: T, next: Option<Box<Node<T>>>) -> Node<T> {
        Node::<T> {value: value, next: next}
    }
}

nix::sys::ptrace::cont:重新启动已停止的跟踪进程,ptrace(PTRACE_CONT, ...)

使用 PID 继续执行进程,(可选) 传递由sig指定的信号

pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()>

Child::kill():强制子进程退出。如果子项已退出,则返回Ok(())

pub fn kill(&mut self) ->Result<()>

nix::sys::ptrace::getregs():获取用户程序的寄存器

pub fn getregs(pid: Pid) -> Result<user_regs_struct>

为了有用,backtrace应该显示函数名和行号,以便程序员可以看到程序的哪些部分正在运行。然而,正在运行的可执行文件仅由汇编指令组成,不知道函数名或行号。为了打印这些信息,我们需要读取存储在为调试而编译的可执行文件中的额外调试符号。这个调试信息存储地址和行号、函数、变量等之间的映射。有了这些信息,我们就可以找到变量在内存中的存储位置,或者根据处理器指令指针的值找出正在执行的是哪一行。

void
bar(int a, int b)
{
    int x, y;
    x = 555;
    y = a+b;
}

void
foo(void) {
    bar(111,222);
}

Stack frame illustration

堆栈帧由两个寄存器限定:%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换成了真正的指令。如果我们在循环或多次调用的函数中设置了断点,这是不理想的!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/239958.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

selenium自动化(中)

显式等待与隐式等待 简介 在实际工作中等待机制可以保证代码的稳定性&#xff0c;保证代码不会受网速、电脑性能等条件的约束。 等待就是当运行代码时&#xff0c;如果页面的渲染速度跟不上代码的运行速度&#xff0c;就需要人为的去限制代码执行的速度。 在做 Web 自动化时…

基于单片机的定时插座在智能家居中的应用

近年来&#xff0c;随着科学技术的发展迅速&#xff0c;人们对智能化的要求越来越高。越来越多的智能化产品进入千家万户&#xff0c;如电脑电视、扫地机器人、智能空气净化器等。这些家居电器和电子产品大都需要连接电源&#xff0c;为满足多种用电器的正常使用&#xff0c;延…

beebox靶场A3 low级别 xss通关教程(二)

六&#xff1a;xss get型 eval 通过观察我们可以发现url地址中存在一个date函数 那我们可以试一下把后面的date()函数去掉&#xff0c;直接写入一个alert(555) 发现直接弹出一个框&#xff0c;证明有xss漏洞 七&#xff1a;xss href 直接进入页面会看到是get方法&#xff0c…

计网 - LVS 是如何直接基于 IP 层进行负载平衡调度

文章目录 模型LVS的工作机制初探LVS的负载均衡机制初探 模型 大致来说&#xff0c;可以这么理解&#xff08;只是帮助我们理解&#xff0c;实际上肯定会有点出入&#xff09;&#xff0c;对于我们的 PC 机来说&#xff0c;物理层可以看成网卡&#xff0c;数据链路层可以看成网卡…

计算机毕业设计 基于SpringBoot的乡村政务办公系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Appium 自动化自学篇 —— 初识Appium自动化!

Appium 简介 随着移动终端的普及&#xff0c;手机应用越来越多&#xff0c;也越来越重要。而作为测试 的我们也要与时俱进&#xff0c;努力学习手机 App 的相关测试&#xff0c;文章将介绍手机自动化测试框架 Appium 。 那究竟什么是 Appium 呢? 接下来我们一起来学习PythonS…

CentOS 7 离线安装MySQL审计插件

命令行 cd /data/toolssz mariadb-10.2.38-linux-x86_64.tar.gztar -zxvf mariadb-10.2.38-linux-x86_64.tar.gzinstall lib/plugin/server_audit.so /usr/lib64/mysql/plugin/mysql -uroot -prootinstall plugin server_audit SONAME server_audit.so;show variables like &q…

【Apollo】ubuntu20.04源码安装apollo8.0

官方源码安装教程 https://blog.csdn.net/weixin_45929038/article/details/120113008 安装NVIDIA GPU驱动 Apollo 8.0 的一些模块的编译和运行需要依赖 NVIDIA GPU 环境&#xff08;例如感知模块&#xff09;&#xff0c;如果有编译和运行这类模块的需求&#xff0c;则需要安…

Redis生产实战-热key、大key解决方案、数据库与缓存最终一致性解决方案

生产环境中热 key 处理 热 key 问题就是某一瞬间可能某条内容特别火爆&#xff0c;大量的请求去访问这个数据&#xff0c;那么这样的 key 就是热 key&#xff0c;往往这样的 key 也是存储在了一个 redis 节点中&#xff0c;对该节点压力很大 那么对于热 key 的处理就是通过热…

深入探索 Spring Boot:简化开发,加速部署的全方位利器

目录 导言 1. 自动配置&#xff08;Auto-Configuration&#xff09; 2. 起步依赖&#xff08;Starter Dependencies&#xff09; 3. 嵌入式 Web 服务器 4. Actuator 5. 外部化配置 6. 简化的安全性配置 7. Spring Boot CLI 8. Spring Boot DevTools 导言 在当今软件开…

【华为数据之道学习笔记】4-2信息架构原则:建立企业层面的共同行为准则

信息架构承载了企业如何管理数据资产的方法&#xff0c;需要从整个企业 层面制订统一的原则&#xff0c;这些原则不仅是对数据专业人员的要求&#xff0c;也是对业务的要求&#xff0c;因为业务才是真正的数据Owner。所以&#xff0c;公司所有业务部门都应该共同遵从信息架构原…

学习pytorch20 pytorch完整的模型验证套路

pytorch完整的模型验证套路 使用非数据集的测试数据&#xff0c;测试训练好模型的效果代码预测结果解决报错 B站小土堆pytorch学习视频 https://www.bilibili.com/video/BV1hE411t7RN/?p32&spm_id_frompageDriver&vd_source9607a6d9d829b667f8f0ccaaaa142fcb 使用非数…

postman接口测试之Postman配置环境变量和全局变量

前言  我们在测试的过程中&#xff0c;遇到最多的问题也可以是环境的问题了吧&#xff0c;今天开发用了这个测试环境&#xff0c;明天又换了另一个测试环境&#xff0c;这样对于我们测试非常的麻烦&#xff0c;特别最接口的时候需要来回的输入环境地址比较麻烦&#xff0c;今天…

基于Python+WaveNet+MFCC+Tensorflow智能方言分类—深度学习算法应用(含全部工程源码)(四)

目录 前言引言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 模型构建3. 模型训练及保存4. 模型生成 系统测试1. 训练准确率2. 测试效果 相关其它博客工程源代码下载其它资料下载 前言 博主前段时间发布了一篇有关方言识别和分类模型训练的博客&#xff…

动态规划学习——通符串匹配,正则表达式

目录 ​编辑 一&#xff0c;通符串匹配 1.题目 2.题目接口 3&#xff0c;解题思路及其代码 二&#xff0c;正则表达 1.题目 2.题目接口 3.解题思路及其代码 三&#xff0c;交错字符串 1.题目 2&#xff0c;题目接口 3.解题思路及其代码 一&#xff0c;通符串匹配 1…

基于Python+WaveNet+MFCC+Tensorflow智能方言分类—深度学习算法应用(含全部工程源码)(三)

目录 前言引言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 模型构建1&#xff09;定义模型结构2&#xff09;优化损失函数 3. 模型训练及保存1&#xff09;模型训练2&#xff09;模型保存3&#xff09;映射保存 相关其它博客工程源代码下载其它资料下载…

让植被管理更精准:数据可视化的新利器

【小编整理了300可视化大屏源文件&#xff0c;需要可后台私~&#xff01;】 在当今时代&#xff0c;数据可视化技术已经成为了一个非常重要的技术。对于植被管理来说&#xff0c;数据可视化也有着非常重要的作用。通过将植被管理数据可视化&#xff0c;我们可以更加清晰地了解植…

Apache Flink(十一):Flink集群部署-Standalone集群部署

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. 节点划分

SpringCloud-高级篇(七)

前面在微服务里整合了Seata&#xff0c;下面利用Seata去解决分布式事务的问题&#xff0c;回去学习Seata中的四种解决方案 &#xff1a;首先学习XA模式 &#xff08;1&#xff09;XA模式 RM在前面讲的是资源管理器&#xff0c;在XA标准中RM都是由数据库来实现的&#xff0c;数…

数据挖掘目标(Kaggle Titanic 生存测试)

import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns1.数据导入 In [2]: train_data pd.read_csv(r../老师文件/train.csv) test_data pd.read_csv(r../老师文件/test.csv) labels pd.read_csv(r../老师文件/label.csv)[Su…