【sylar-webserver】8 HOOK模块

文章目录

  • 知识点
    • HOOK实现方式
      • 非侵入式hook
      • 侵入式hook ⭐⭐⭐
    • 覆盖系统调用接口
    • 获取被全局符号介入机制覆盖的系统调用接口
  • 具体实现
    • C++ 模板成员函数继承 和 成员函数指针类型匹配 ⭐⭐⭐⭐⭐
    • FdCtx 和 FdManager ⭐⭐
      • 判断socket的小技巧
      • FdCtx
      • FdManager
    • connect hook ⭐
    • do_io模板 ⭐⭐⭐⭐⭐
    • 记录超时信息,阻塞信息

在写之前模块的时候,我一直在困惑 协程是如何高效工作的,毕竟协程阻塞线程也就阻塞了。
HOOK模块解开了我的困惑。😎

知识点

HOOK实现方式

动态链接中的hook实现

hook的实现机制,通过动态库的全局符号介入功能,用自定义的接口来替换掉同名的系统调用接口。由于系统调用接口基本上是由C标准函数库 libc 提供的,所以这里要做的事情就是用自定义的动态库来覆盖掉 libc 中的同名符号。

基于动态链接的hook有两种方式:

非侵入式hook

第一种是外挂式hook,也称为非侵入式hook,通过优先加自定义载动态库来实现对后加载的动态库进行hook,这种hook方式不需要重新编译代码,考虑以下例子:

#include <unistd.h>
#include <string.h>int main(){write(STDOUT_FILENO, "hello world\n", strlen("hello world\n")); // 调用系统调用write写标准输出文件描述符return 0;
}

编译运行

# gcc main.c
# ./a.out
hello world

ldd命令查看可执行程序的依赖的共享库

# ldd ./a.out linux-vdso.so.1 (0x00007ffde42a4000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f80ec76e000)/lib64/ld-linux-x86-64.so.2 (0x00007f80ecd61000)

可以看到其依赖libc共享库,write系统调用就是由libc提供的。

下面在不重新编译代码的情况下,用自定义的动态库来替换掉可执行程序a.out中的write实现,新建hook.cc

#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>ssize_t write(int fd, const void *buf, size_t count) {syscall(SYS_write, STDOUT_FILENO, "12345\n", strlen("12345\n"));
}
gcc -fPIC -shared hook.cc -o libhook.so	# 把hook.cc编译成动态库

通过设置 LD_PRELOAD环境变量,将libhoook.so设置成优先加载,从面覆盖掉libc中的write函数,如下:

# LD_PRELOAD="./libhook.so" ./a.out 
12345

LD_PRELOAD环境变量,它指明了在运行a.out之前,系统会优先把libhook.so加载到了程序的进程空间,使得在a.out运行之前,其全局符号表中就已经有了一个write符号,这样在后续加载libc共享库时,由于全局符号介入机制,libc中的write符号不会再被加入全局符号表,所以全局符号表中的write就变成了我们自己的实现。⭐

侵入式hook ⭐⭐⭐

libco,libgo 也是使用这种方式

第二种方式的hook是侵入式的,需要改造代码或是重新编译一次以指定动态库加载顺序。

覆盖系统调用接口

unsigned int sleep(unsigned int seconds){... 
}

直接写入文件,只需要比 libc 提前链接即可。

获取被全局符号介入机制覆盖的系统调用接口

dslym 函数原型

#define _GNU_SOURCE
#include <dlfcn.h>void *dlsym(void *handle, const char *symbol);
  • 链接需要指定 -ldl 参数。
  • 使用dlsym找回被覆盖的符号,第一个参数固定为RTLD_NEXT,第二个参数是符号的名称。

具体实现

CMakeLists.txt

set(LIBSsylaryaml-cpppthreaddl
)
extern "C"{// sleep // 定义了函数指针类型 sleep_fun// 该类型对应原生 sleep 函数的签名(接收 unsigned int 参数,返回 unsigned int)typedef unsigned int (*sleep_fun)(unsigned int seconds);// 声明外部的全局函数指针变量 sleep_f,用于保存原始 sleep 函数的地址// 通过 sleep_f 仍能调用原版函数extern sleep_fun sleep_f;
}
#define HOOK_FUN(XX) \XX(sleep)void hook_init(){static bool is_inited = false;if(is_inited){return;}//保存原函数:hook_init() 通过 dlsym(RTLD_NEXT, "sleep") 获取系统原版 sleep 函数的地址,保存到 sleep_f 指针
#define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT, #name);HOOK_FUN(XX);
#undef XX 
}extern "C" {
#define XX(name) name ## _fun name ## _f = nullptr;		// 初始化 sleep_fun sleep_f = nullptr;HOOK_FUN(XX);
#undef XX// sleep
unsigned int sleep(unsigned int seconds){if(!sylar::t_hook_enable){return sleep_f(seconds);}sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();sylar::IOManager* iom = sylar::IOManager::GetThis();/*** C++规定成员函数指针的类型包含类信息,即使存在继承关系,&IOManager::schedule 和 &Scheduler::schedule 属于不同类型。* 通过强制转换,使得类型系统接受子类对象iom调用基类成员函数的合法性。* * schedule是模板函数* 子类继承的是模板的实例化版本,而非原始模板* 直接取地址会导致函数签名包含子类类型信息* * std::bind 的类型安全机制* bind要求成员函数指针类型与对象类型严格匹配。当出现以下情况时必须转换:* * 总结,当需要绑定 子类对象调用父类模板成员函数,父类函数需要强转成父类* (存在多继承或虚继承导致this指针偏移)* * 或者* std::bind(&Scheduler::schedule, static_cast<Scheduler*>(iom), fiber, -1)* */iom->addTimer(seconds * 1000 , std::bind((void(sylar::Scheduler::*)(sylar::Fiber::ptr, int thread))&sylar::IOManager::schedule, iom, fiber, -1));sylar::Fiber::GetThis()->yield();return 0;
}

C++ 模板成员函数继承 和 成员函数指针类型匹配 ⭐⭐⭐⭐⭐

  1. 模板成员函数继承的指针类型问题
// 基类 Scheduler 的模板函数
class Scheduler {
public:template<class FiberOrCb>void schedule(FiberOrCb fc, int thread = -1); // 模板函数
};// 子类 IOManager 继承模板函数的实例化版本
class IOManager : public Scheduler {// 继承 schedule<sylar::Fiber::ptr> 的实例化版本
};
  • 问题本质:当子类继承模板成员函数时,&IOManager::schedule 的类型实际上时 void (IOManager::*)(sylar::Fiber::ptr, int)
  • (我的理解是模板函数的参数类型不确定,必须显示的转成确定的函数类型指针)
  • 类型不匹配:std::bind 要求成员函数指针类型必须与对象类型严格匹配。
  1. 多继承场景下的 this 指针偏移风险
// 强制转换的语法含义
(void(sylar::Scheduler::*)(sylar::Fiber::ptr, int)) &sylar::IOManager::schedule
  • 类型安全:通过强制转换为基类成员函数指针类型:
    • 确保调用时正确进行 this 指针调整
    • 避免多继承场景下潜在的指针偏移错误
iom->addTimer(usec / 1000,std::bind((void(Scheduler::*)(Fiber::ptr, int))  // 关键转换&IOManager::schedule,  // 原始成员函数指针iom,   // IOManager* 类型的对象fiber, // 参数1-1     // 参数2)
);

FdCtx 和 FdManager ⭐⭐

FdManager::get(fd) | ||
new FdCtx(fd) | ||
FdCtx::init()	// 获取到fd的基础信息 m_isInit,m_isSocket,m_sysNonblock(默认true), m_userNonblock(默认false,通过hook fcntl操作记录),m_isClosed, m_recvTimeout(-1),m_sendTimeout(-1)

m_userNonblock 阻塞属性 通过 hook fcntl -> setUserNonblock ⭐
m_recvTimeout,m_sendTimeout 超时事件 通过 hook setsockopt -> setTimeout 设置⭐

判断socket的小技巧

		/*** stat族 ⭐* * 获取fd信息* int fstat(int filedes, struct stat *buf);* 返回值: 执行成功则返回0,失败返回-1,错误代码存于errno* * 查看 stat 里的 st_mode 属性* * 常用宏S_ISLNK(st_mode):是否是一个连接.S_ISREG是否是一个常规文件.S_ISDIR是否是一个目录S_ISCHR是否是一个字符设备.S_ISBLK是否是一个块设备S_ISFIFO是否是一个FIFO文件.S_ISSOCK是否是一个SOCKET文件. */struct stat fd_stat;if(-1 == fstat(m_fd, &fd_stat)){m_isInit = false;m_isSocket = false;}else{m_isInit = true;m_isSocket = S_ISSOCK(fd_stat.st_mode);}

FdCtx


class FdCtx : public std::enable_shared_from_this<FdCtx>{
public:typedef std::shared_ptr<FdCtx> ptr;FdCtx(int fd);~FdCtx();bool init();bool isInit() const {return m_isInit;}bool isSocket() const {return m_isSocket;}bool isClose() const {return m_isClosed;}void setUserNonblock(bool v){m_userNonblock = v;}bool getUserNonblock() const {return m_userNonblock;}void setSysNonblock(bool v){m_sysNonblock = v;}bool getSysNonblock() const {return m_sysNonblock;}/*** @brief 设置超时时间* @param[in] type 类型SO_RCVTIMEO(读超时), SO_SNDTIMEO(写超时)* @param[in] v 时间毫秒*/void setTimeout(int type, uint64_t v);/*** @brief 获取超时时间* @param[in] type 类型SO_RCVTIMEO(读超时), SO_SNDTIMEO(写超时)* @return 超时时间毫秒*/int getTimeout(int type);private:// 使用位域,可能考虑到会有大量的fd连接,节省空间。⭐bool m_isInit: 1;bool m_isSocket: 1;bool m_sysNonblock: 1;   // 是否 hook 非阻塞bool m_userNonblock: 1;  // 是否 用户主动设置 非阻塞bool m_isClosed: 1;int m_fd;uint64_t m_recvTimeout;  // 读超时时间毫秒uint64_t m_sendTimeout;  // 写超时时间毫秒
};

FdManager

class FdManager{
public:typedef RWMutex RWMutexType;FdManager();/*** @brief 获取/创建文件句柄类FdCtx* @param[in] fd 文件句柄* @param[in] auto_create 是否自动创建* @return 返回对应文件句柄类FdCtx::ptr*/FdCtx::ptr get(int fd, bool auto_create = false);/*** @brief 删除文件句柄类* @param[in] fd 文件句柄*/void del(int fd);private:RWMutexType m_mutex;std::vector<FdCtx::ptr> m_datas;
};

connect hook ⭐

int connect_with_timeout(int fd, const struct sockaddr *addr, socklen_t addrlen, uint64_t timeout_ms){... /*** 非阻塞connect调用会立即返回EINPROGRESS错误码,表示连接正在建立* 此时不需要也不能重复调用connect,否则可能触发EALREADY错误 (和 do_io 不同的地方) ⭐* 通过等待WRITE事件即可判断连接是否建立完成*/int n = connect_f(fd, addr, addrlen);if(n == 0){return 0;}else if(n != -1 || errno != EINPROGRESS){ return n;}// 下面和 do_io 类似sylar::IOManager* iom = sylar::IOManager::GetThis();sylar::Timer::ptr timer;std::shared_ptr<timer_info> tinfo(new timer_info);std::weak_ptr<timer_info> winfo(tinfo);if(timeout_ms != (uint64_t)-1){iom->addConditionTimer(timeout_ms, [winfo, fd, iom](){auto it = winfo.lock();if(!it || it->cancelled){return;}it->cancelled = ETIMEDOUT;iom->cancelEvent(fd, sylar::IOManager::Event::WRITE);}, winfo);}int rt = iom->addEvent(fd, sylar::IOManager::Event::WRITE);if(rt == 0){sylar::Fiber::GetThis()->yield();if(timer){timer->cancel();}if(tinfo->cancelled){errno = tinfo->cancelled;return -1;}}else{if(timer) {timer->cancel();}SYLAR_LOG_ERROR(g_logger) << "connect addEvent(" << fd << ", WRITE) error";}int error = 0;socklen_t len = sizeof(int);// 非阻塞 connect 操作返回 EINPROGRESS 后,通过监听写事件完成连接建立,此时需要检查实际连接结果if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)){return -1;}if(!error){return 0;}else{errno = error;return -1;}
}int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){// static uint64_t s_connect_timeout = -1;return connect_with_timeout(sockfd, addr, addrlen, sylar::s_connect_timeout);
}

do_io模板 ⭐⭐⭐⭐⭐

/*** 重点 !!!* * 模板函数,通用的 read-write api hook 操作* * Args&& 万能引用,根据传入实参自动推导* * 这里Args,可能是左值,也可能是右值* * std::forward 保持参数的原始值类别 */ 
template<typename OriginFun, typename ... Args> // 常用⭐
static ssize_t do_io(int fd, 						OriginFun fun, 					// hook的原库函数const char* hook_fun_name, 		// debug输出,hook的函数名uint32_t event, int timeout_so,     			// 读 / 写 超时 宏标签Args&&... args)
{// Scheduler::run() 设置当前线程是否hook ⭐if(!sylar::t_hook_enable){return fun(fd, std::forward<Args>(args)...);}// fd 添加到 FdMgrsylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx){return fun(fd, std::forward<Args>(args)...);}// 如果ctx关闭if(ctx->isClose()){errno = EBADF;return -1;}// 不是 socket 或者 用户设定了非阻塞// 用户设定了非阻塞,意味着自行处理非阻塞逻辑f(!ctx->isSocket() || ctx->getUserNonblock()){return fun(fd, std::forward<Args>(args)...);}uint64_t to = ctx->getTimeout(timeout_so);	// 获取时间超时时间,通过setsockopt hook写入/*
struct timer_info{int cancelled = 0;
};*/std::shared_ptr<timer_info> tinfo(new timer_info);retry:ssize_t n = fun(fd, std::forward<Args>(args)...);while(n == -1 && errno == EINTR){		// 系统调用被信号中断n = fun(fd, std::forward<Args>(args)...);}if(n == -1 && errno == RAGAIN){			// 非阻塞操作无法立即完成sylar::IOManager* iom = sylar::IOManager::GetThis();sylar::Timer::ptr timer;std::weak_ptr<timer_info> winfo(tinfo);if(to != (uint64_t)-1){// 添加一个条件定时器,如果 tinfo 还在意味着 fd还没等到event触发。// 到了超时时间,就直接取消事件。timer = iom->addConditionTimer(to, [iom, winfo, fd, event](){auto it = winfo.lock();if(!it || it->cancelled){ // 双重验证⭐return;}it->cancelled = ETIMEDOUT;// cancelEvent 取消事件触发条件,直接触发事件 ⭐iom->cancelEvent(fd, (sylar::IOManager::Event)event);}, winfo);}// 没传入fd,把当前协程传入。当事件触发,会回到这个协程继续运行int rt = iom->addEvent(fd, (sylar::IOManager::Event)event); // 正式 注册事件 ⭐if(rt != 0){	// 添加失败// 定时器删除if(timer){timer->cancel();   // 删除定时器的权利 交给了定时器}return rt;}else{sylar::Fiber::GetThis()->yield();/*再次回到这里,两种情况:1. 定时器触发之前,事件触发2. 定时器触发,事件超时*/if(timer){timer->cancel();}if(tinfo->cancelled){		// 2. 超时errno = tinfo->cancelled;return -1;}goto retry;		// 1. 重新操作 fd}}return n;
}

使用案例

int accept(int s, struct sockaddr *addr, socklen_t *addrlen){int fd = do_io(s, accept_f, "accept", sylar::IOManager::Event::READ, SO_RCVTIMEO, addr, addrlen);if(fd != -1){sylar::FdMgr::GetInstance()->get(fd, true);}return fd;
}ssize_t write(int fd, const void *buf, size_t count){return do_io(fd, write_f, "write", sylar::IOManager::Event::WRITE, SO_SNDTIMEO, buf, count);
}

记录超时信息,阻塞信息

// 增加fd事件超时选项,设置了超时事件,上面的hook才会有定时器,不然fd事件会一直存在
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen){if(!sylar::t_hook_enable){return setsockopt_f(sockfd, level, optname, optval, optlen);}if(level == SOL_SOCKET){if(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO){   // 超时事件设置sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(sockfd);if(ctx){const timeval* v = (const timeval*)optval;ctx->setTimeout(optname, v->tv_sec* 1000 + v->tv_usec / 1000);}}}return setsockopt_f(sockfd, level, optname, optval, optlen);
}
int fcntl(int fd, int cmd, ... /* arg */ ){va_list va;va_start(va, cmd);switch(cmd){case F_SETFL:{int arg = va_arg(va, int);va_end(va);// 获取 FdCtxsylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx || ctx->isClose() || !ctx->isSocket()){return fcntl_f(fd, cmd, arg);}// 检查args,用户是否设置 非阻塞。// FdCtx里的m_userNonblock,这里设置。 ⭐ctx->setUserNonblock(arg & O_NONBLOCK);// 要执行了,所以把 hook 非阻塞直接加上。if(ctx->getSysNonblock()){arg |= O_NONBLOCK;}else{arg &= ~O_NONBLOCK;}return fcntl_f(fd, cmd, arg);}break;case F_GETFL:{va_end(va);int arg = fcntl_f(fd, cmd);sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);if(!ctx || ctx->isClose() || !ctx->isSocket()){return arg;}// 设置 用户是否判断 非阻塞。if(ctx->getUserNonblock()){return arg | O_NONBLOCK;}else{ // 如果之前就没有,那么需要恢复默认。(Hook默认加上了非阻塞)⭐return arg & ~O_NONBLOCK;}}break;case F_DUPFD:case F_DUPFD_CLOEXEC:case F_SETFD:case F_SETOWN:case F_SETSIG:case F_SETLEASE:case F_NOTIFY:#ifdef F_SETPIPE_SZcase F_SETPIPE_SZ:#endif{int arg = va_arg(va, int);va_end(va);return fcntl_f(fd, cmd, arg); }break;case F_GETFD:case F_GETOWN:case F_GETSIG:case F_GETLEASE:#ifdef F_GETPIPE_SZcase F_GETPIPE_SZ:#endif{va_end(va);return fcntl_f(fd, cmd);}break;case F_SETLK:case F_SETLKW:case F_GETLK:{struct flock* arg = va_arg(va, struct flock*);va_end(va);return fcntl_f(fd, cmd, arg);}break;case F_GETOWN_EX:case F_SETOWN_EX:{struct f_owner_exlock* arg = va_arg(va, struct f_owner_exlock*);va_end(va);return fcntl_f(fd, cmd, arg);}break;default:va_end(va);return fcntl_f(fd, cmd);}
}// ioctl 用于 设备驱动程序中设备控制接口函数 ⭐ 没用过
int ioctl(int d, unsigned long int request, ...){va_list va;va_start(va, request);void* arg = va_arg(va, void*);va_end(va);// FIONBIO(设置非阻塞模式)if(FIONBIO == request){ // 主要用于处理文件描述符的非阻塞模式设置bool user_nonblock = !!*(int*)arg;   // 将参数转换为布尔值sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(d);if(!ctx || ctx->isClose() || !ctx->isSocket()){return ioctl_f(d, request, arg);}ctx->setUserNonblock(user_nonblock);}return ioctl_f(d, request, arg);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringAI+DeepSeek大模型应用开发——1 AI概述

AI领域常用词汇 LLM&#xff08;LargeLanguage Model&#xff0c;大语言模型&#xff09; 能理解和生成自然语言的巨型AI模型&#xff0c;通过海量文本训练。例子&#xff1a;GPT-4、Claude、DeepSeek、文心一言、通义干问。 G&#xff08;Generative&#xff09;生成式: 根据上…

SpringBoot 基本原理

SpringBoot 为我们做的自动配置&#xff0c;确实方便快捷&#xff0c;但一直搞不明白它的内部启动原理&#xff0c;这次就来一步步解开 SpringBoot 的神秘面纱&#xff0c;让它不再神秘。 目录 SpringBootApplication 背后的秘密 Configuration ComponentScan EnableAutoC…

2025.4.17总结

工作&#xff1a;今天对需求的测试设计进行了完善&#xff0c;然后&#xff0c;对测试设计进行了评审&#xff0c;最后提了个问题单。 反思这个过程&#xff0c;要说不足的地方&#xff0c;就是评审的时候总觉得自己吐字不清晰&#xff0c;表达能力早就想提升了&#xff0c;但…

2021-11-14 C++三七二十一数

缘由c编程怎么写&#xff0c;紧急求解-编程语言-CSDN问答 void 三七二十一数() {//缘由https://ask.csdn.net/questions/7566632?spm1005.2025.3001.5141int n 0, a 0, b 0, p 1;std::cin >> n;while (n--){std::cin >> a >> b;while (a<b){if (a %…

大模型面经 | DeepSpeed中ZeRO-1、ZeRO-2和ZeRO-3的区别是什么?

大家好,我是皮先生!! 今天给大家分享一些关于大模型面试常见的面试题,希望对大家的面试有所帮助。 往期回顾: 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题一) 大模型面经 | 春招、秋招算法面试常考八股文附答案(RAG专题二) 大模型面经 | 春招、秋招算法…

spring boot 文件上传

1.编写文件上传的表单页面 <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><meta http-equiv"Content-Type" content"text/html; charsetUTF-8&qu…

机器学习核心算法全解析:从基础到进阶的 18 大算法模型

在机器学习领域&#xff0c;算法模型是解决实际问题的核心工具。 不同的算法适用于不同的数据场景和任务需求&#xff0c;理解它们的原理与应用是掌握机器学习的关键。 以下将详细解析 18 个核心算法模型&#xff0c;涵盖监督学习、无监督学习、集成学习和深度学习等多个领域…

5G网络切片:精准分配资源,提升网络效率的关键技术

5G网络切片&#xff1a;精准分配资源&#xff0c;提升网络效率的关键技术 随着5G技术的广泛应用&#xff0c;网络切片&#xff08;Network Slicing&#xff09;作为其核心创新之一&#xff0c;正在改变传统网络架构。它通过将物理网络划分为多个逻辑网络&#xff08;切片&…

Spring Boot中Excel处理完全指南

文章目录 1. Excel处理基础知识1.1 为什么需要在应用中处理Excel文件&#xff1f;1.2 Java中的Excel处理库介绍1.2.1 Apache POI1.2.2 EasyExcel1.2.3 JExcel1.2.4 Apache POI SXSSF 1.3 Spring Boot中集成Excel处理 2. 在Spring Boot中集成Excel处理库2.1 集成Apache POI2.1.1…

Elasticsearch 8.18 中提供了原生连接 (Native Joins)

作者&#xff1a;来自 Elastic Costin Leau 探索 LOOKUP JOIN&#xff0c;这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令。 很高兴宣布 LOOKUP JOIN —— 这是一条在 Elasticsearch 8.18 的技术预览中提供的新 ES|QL 命令&#xff0c;旨在执行左 joins 以进行…

2025年渗透测试面试题总结-拷打题库03(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库03 一、Windows与Linux系统提权思路 Windows提权 Linux提权 二、…

【华为】OSPF震荡引起CPU占用率高怎么解决?

原创&#xff1a;厦门微思网络 现象描述 如图所示&#xff0c;Switch_1、Switch_2、Switch_3和Switch_4配置了OSPF协议&#xff0c;发现Switch_1设备的CPU占用率高&#xff0c;ROUT任务占用率明显高于其他任务并且产生路由震荡。 故障组网图 原因分析 网络中IP地址冲突导致…

Everything 安装教程与使用教程(附安装包)

文章目录 前言一、Everything 介绍二、Everything 安装教程1.Everything 安装包下载2.选择安装文件3.选择安装语言4.接受许可协议5.选择安装位置6.配置安装选项7.完成安装 三、Everything 使用教程1.启动软件2.简单关键词搜索3.按类型搜索 前言 在日常使用电脑时&#xff0c;随…

极狐GitLab CI/CD 流水线计算分钟数如何管理?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 计算分钟管理 (PREMIUM SELF) 在极狐GitLab 16.1 中&#xff0c;从 CI/CD 分钟数重命名为计算配额或计算分钟数。 管理员可…

Containerd 1.7.2 离线安装与配置全指南(生产级优化)

Containerd 1.7.2 离线安装与配置全指南&#xff08;生产级优化&#xff09; 摘要&#xff1a;本文详细讲解在无外网环境下部署 Containerd 1.7.2 容器运行时的完整流程&#xff0c;涵盖二进制包安装、私有镜像仓库配置、Systemd服务集成等关键步骤&#xff0c;并提供生产环境…

33-公交车司机管理系统

技术&#xff1a; 基于 B/S 架构 SpringBootMySQLvueelementui 环境&#xff1a; Idea mysql maven jdk1.8 node 用户端功能 1.首页:展示车辆信息及车辆位置和线路信息 2.模块:车辆信息及车辆位置和线路信息 3.公告、论坛 4.在线留言 5.个人中心:修改个人信息 司机端功能…

基于 OpenCV 的图像与视频处理

基于 OpenCV 的图像处理 一、实验背景 OpenCV 是一个开源的计算机视觉库&#xff0c;广泛应用于图像处理、视频分析、目标检测等领域。通过学习 OpenCV&#xff0c;可以快速实现图像和视频的处理功能&#xff0c;为复杂的应用开发 奠定基础。本实验旨在通过实际代码示例&…

Linux 常用指令用户手册

Linux 常用指令用户手册 适合新手入门 & 日常速查 目录 基础操作文件与目录管理权限与所有权文本处理压缩与解压系统监控网络操作进程管理实用小技巧 1. 基础操作 1.1 查看系统信息 # 查看内核版本 uname -a# 查看系统发行版信息&#xff08;适用于 Debian/Ubuntu&…

长效IP与短效IP:如何选择适合业务的代理类型

在当今数据驱动的互联网环境中&#xff0c;代理IP已成为企业运营、数据采集和网络安全的关键工具。其中长效IP与短效IP作为两种主流代理类型&#xff0c;因特性差异被应用于不同场景。本文将深入解析二者的区别&#xff0c;并提供实际场景中的选择建议。 一、长效IP与短效IP&a…

数据结构|排序算法(三)选择排序 堆排序 归并排序

一、选择排序 1.算法思想 选择排序&#xff08;Selection Sort&#xff09;是一种简单直观的排序算法&#xff0c;其基本思想是&#xff1a;每次都从待排序部分中选出最小的一个数据和待排序的第一个数据交换。 将待排序序列分为已排序和未排序两部分&#xff0c;初始时已排…