网站建设与管理总结心得,技术支持 如皋网站建设,重庆新闻发布会最新,南山做网站多少钱文中code https://gitee.com/bbjg001/darcy_common/tree/master/io_hook
需求引入
最近工作需要#xff0c;需要验证一下我们的服务在硬盘故障下的鲁棒性。
从同事大佬哪里了解到hook技术#xff0c;可以通过LD_PRELOAD这个环境变量拦截依赖库的调用链#xff0c;将对标准…文中code https://gitee.com/bbjg001/darcy_common/tree/master/io_hook
需求引入
最近工作需要需要验证一下我们的服务在硬盘故障下的鲁棒性。
从同事大佬哪里了解到hook技术可以通过LD_PRELOAD这个环境变量拦截依赖库的调用链将对标准库函数的调用转移到自己自定义的函数然后返回自定义的错误代码。
使用方式
export LD_PRELOADhook_lib.so
./main
# hook_lib.so是自行编译的用来拦截的so文件
# ./main是要运行的二进制文件一个简单的例子
有这样一个简单的main函数
// main.cpp
#include fcntl.h
#include unistd.h
#include iostream
#include string.hint main()
{int rfd open(test.txt, O_RDONLY);std::cout open(), with ret: rfd , err( errno ): strerror(errno) std::endl;close(rfd);
}如果test.txt不存在输出是这样的 如果test.txt是一个存在的正常文件输出 下面自定义open函数通过hook拦截调用链调用自己的open函数找到open函数的定义 自定义open函数注意函数传参要与原open函数一致
// hook_lib.cpp
#include unistd.h
#include iostream
#include dlfcn.h
#include fcntl.htypedef int(*OPEN)(const char *__path, int __oflag, ...);int open(const char *__path, int __oflag, ...){std::cout !!! open函数被拦截了 std::endl;errno 2;return -1;
}正常编译并运行main.cpp
g main.cpp -o main ./main输出是正常的 下面拦截注入自己的open函数
# 把自己的函数文件编译成.so文件
g --shared -fPIC hook_lib.cpp -o hook_lib.so -ldl
# 通过LD_PRELOAD拦截调用链启动main函数
LD_PRELOAD./hook_lib.so ./main将hook函数的文件编译成.so文件
g --shared -fPIC hook_lib.cpp -o hook_lib.so -ldl在启动时通过LD_PRELOAD指定hook的库文件
g main.cpp -o main
LD_PRELOAD./hook_lib.so ./main进一步的做更多自定义的逻辑
这次以write函数为例
返回正常的write函数
可以定义在某些情况下返回错误码某些情况下返回正常的write函数。这里通过随机概率返回两者。
hook逻辑
// hook_lib
extern ssize_t std_write (int __fd, __const void *__buf, size_t __n) {static void *handle NULL;static WRITE old_write NULL;if (!handle) {handle dlopen(libc.so.6, RTLD_LAZY);old_write (WRITE)dlsym(handle, write);}return old_write(__fd, __buf, __n);
}
// 模拟的write函数
extern ssize_t write (int __fd, __const void *__buf, size_t __n) {if (rand() % 100 / 100.0 0.5) {errno 2;return -1;}return std_write(__fd, __buf, __n);
}main函数
// main
int main(int argc, char *argv[]){srand(time(NULL));const char *f_path test.txt;int fd open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);for (int i 0; i 10; i){int ret write(fd, HelloWorld, 10);if (ret 0){std::cout write(), with ret: ret , err_info: strerror(errno) std::endl;}else{std::cout write(), with ret: ret std::endl;}}close(fd);return 0;
}执行结果
$ LD_PRELOAD./hook_lib.so ./main
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10控制注入异常的path
hook逻辑
// hook_lib
// 检查当前操作的文件是否是要注入异常的文件
bool is_current_path(int fd, std::string path){ if(path){return false;}// get current pathchar buf[256] {\0};char _file_path[256] {\0};std::string file_path;snprintf(buf, sizeof (buf), /proc/self/fd/%d, fd);if (readlink(buf, _file_path, sizeof(_file_path) - 1) ! -1) {file_path _file_path;}if(file_path.find(path) ! std::string::npos){ // 路径中包含${path}即被命中return true;}return false;
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {if (is_current_path(__fd, test)) {errno 2;return -1;}return std_write(__fd, __buf, __n);
}main函数
// main
int main(int argc, char *argv[]){const char *f_path argv[1];int fd open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret write(fd, HelloWorld, 10);if (ret 0){std::cout write(), with ret: ret , err_info: strerror(errno) std::endl;}else {std::cout write(), with ret: ret std::endl;}close(fd);return 0;
}执行结果
$ LD_PRELOAD./hook_lib.so ./main test.txt
write(), with ret: -1, err_info: No such file or directory$ LD_PRELOAD./hook_lib.so ./main newfile.txt
write(), with ret: 10延时返回
这里比较简单不再做代码示例
sleep(time_s); // 秒
usleep(time_ms); // 微秒动态控制异常注入
希望能从第三方位置读取配置通过变更配置动态的对指定path注入指定的错误(码)类型。
从文件获得配置
hook逻辑
// hook_lib
void get_ctrl_var_file(std::string *path, int *eno, int *sleep_time){std::ifstream ifs(conf.txt);ifs *path;ifs *eno;ifs *sleep_time;ifs.close();
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {std::string epath;int eno, ehang_time;get_ctrl_var_file(epath, eno, ehang_time);if (is_current_path(__fd, epath)) {errno eno;hang_sleep(ehang_time);return -1;}return std_write(__fd, __buf, __n);
}main函数
// main
int main(int argc, char *argv[]){const char *f_path argv[1];int fd open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret write(fd, HelloWorld, 10);if (ret 0){std::cout write(), with ret: ret , err_info: strerror(errno) std::endl;}else {std::cout write(), with ret: ret std::endl;}close(fd);return 0;
}conf.txt
test.txt 2 1000000执行结果
$ LD_PRELOAD./hook_lib.so ./main test.txt
write(), with ret: -1, err_info: No such file or directory$ LD_PRELOAD./hook_lib.so ./main newfile.txt
write(), with ret: 10从redis获得配置
hook逻辑
#include hiredis/hiredis.h
// hook_lib
void get_ctrl_var_redis(std::string *path, int *eno, int *sleep_time){redisContext *conn redisConnect(127.0.0.1, 6379);if(conn ! NULL conn-err){printf(connection error: %s\n,conn-errstr);return;}redisReply *reply (redisReply*)redisCommand(conn,get %s, /hook/write/epath);*path reply-str;reply (redisReply*)redisCommand(conn,get %s, /hook/write/eno);*eno std::atoi(reply-str);reply (redisReply*)redisCommand(conn,get %s, /hook/write/ehang);*sleep_time std::atoi(reply-str);freeReplyObject(reply);redisFree(conn);
}extern ssize_t write (int __fd, __const void *__buf, size_t __n) {std::string epath;int eno, ehang_time;get_ctrl_var_redis(epath, eno, ehang_time);if (is_current_path(__fd, epath)) {errno eno;hang_sleep(ehang_time);return -1;}return std_write(__fd, __buf, __n);
}main函数
// main
int main(int argc, char *argv[]){const char *f_path argv[1];int fd open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);int ret write(fd, HelloWorld, 10);if (ret 0){std::cout write(), with ret: ret , err_info: strerror(errno) std::endl;}else {std::cout write(), with ret: ret std::endl;}close(fd);return 0;
}在redis中添加如下变量
set /hook/write/epath test.txt
set /hook/write/eno 5
set /hook/write/ehang 1000000执行结果
$ LD_PRELOAD./hook_lib.so ./main test.txt
write(), with ret: -1, err_info: Input/output error$ LD_PRELOAD./hook_lib.so ./main newfile.txt
write(), with ret: 10in mac os
在mac os中需要使用其他的环境变量进行注入简单试了下没能成功抛砖引玉
https://stackoverflow.com/questions/34114587/dyld-library-path-dyld-insert-libraries-not-working
参考
https://blog.51cto.com/u_15703183/5464438
https://sq.sf.163.com/blog/article/173506648836333568
https://xz.aliyun.com/t/6883
https://www.cnblogs.com/wainiwann/p/3340277.html