公司网站制作公司,wordpress是主流cms系统,网站seo分析,怎么看wordpress用了哪个主题程序流程
执行文件访问拦截和 IP 地址拦截的流程#xff1a;
文件访问拦截功能#xff1a; 当应用程序尝试执行文件操作#xff0c;例如打开文件#xff0c;调用的是 open 或 openat 函数。 由于这两个函数已经被重定向为自定义的版本#xff0c;所以实际上调用的是 op…程序流程
执行文件访问拦截和 IP 地址拦截的流程
文件访问拦截功能 当应用程序尝试执行文件操作例如打开文件调用的是 open 或 openat 函数。 由于这两个函数已经被重定向为自定义的版本所以实际上调用的是 open 或 openat 函数的自定义替代版本。 自定义的 open 或 openat 函数首先检查传递的文件路径和执行文件操作的进程是否符合访问控制策略。 它检查文件路径是否在黑名单或白名单中并检查执行文件操作的进程是否在白名单中。如果文件或进程不符合策略函数会拒绝文件操作。 如果文件和进程都符合策略它会记录文件访问操作的结果然后调用真正的 open 或 openat 函数执行操作。 如果文件或进程不符合策略它会记录拒绝的结果并设置 errno 为 EPERM表示权限被拒绝。
IP 地址拦截功能 当应用程序尝试执行网络连接操作例如使用 connect 函数或发送数据到远程主机调用的是 connect 或 sendto 函数。 与文件访问拦截类似这两个函数也已被重定向为自定义版本因此实际上调用的是自定义的 connect 或 sendto 函数。 自定义的 connect 或 sendto 函数首先检查传递的目标 IP 地址以及执行连接或数据发送操作的进程是否符合访问控制策略。 它检查 IP 地址是否在黑名单或白名单中并检查执行操作的进程是否在白名单中。如果 IP 地址或进程不符合策略函数会拒绝操作。 如果 IP 地址和进程都符合策略它会记录 IP 操作的结果然后调用真正的 connect 或 sendto 函数执行操作。 如果 IP 地址或进程不符合策略它会记录拒绝的结果并设置 errno 为 EPERM表示权限被拒绝。
总的来说这两段代码的流程是先检查文件或 IP 地址是否符合访问控制策略如果符合则允许操作并记录结果如果不符合则拒绝操作并记录结果以确保系统的安全性和遵守访问策略。
libhook.cpp
//验证文件访问是否受到权限限制
static bool
allow_open(const char *exe, const char *path, string return_full_path) noexcept(true)
{try{// 检查传入的参数是否为NULLif (exe NULL || path NULL)return false;// 获取目标文件的绝对路径const string real_absolute_path get_real_path(get_absolute_path(path).c_str());return_full_path real_absolute_path;// 遍历文件黑名单检查是否有文件受黑名单保护for (auto i : file_black_list()){if (i.first real_absolute_path){// 文件在黑名单中检查程序是否在黑名单if (i.second.count(exe) 0)return false;}}// 遍历文件白名单检查是否有文件受白名单保护for (auto i : file_white_list()){if (i.first real_absolute_path){// 文件在白名单中检查程序是否不在白名单if (i.second.count(exe) 0)return false;}}// 文件访问权限通过返回truereturn true;}catch (...){// 捕获异常如果出现异常也返回truereturn true;}
}这个函数接受两个C风格字符串exe 和 path以及一个字符串引用参数return_full_path。函数首先检查传入的exe和path是否为NULL如果是NULL则直接返回false表示不允许访问。然后它获取目标文件的绝对路径将其保存到real_absolute_path变量中并将其赋值给return_full_path以便后续使用。函数接着遍历文件黑名单file_black_list()和文件白名单file_white_list()分别检查目标文件是否在黑名单或白名单中以及程序是否在对应的名单中。如果文件在黑名单中并且程序也在黑名单中或者文件在白名单中但程序不在白名单中则返回false表示不允许访问。最后如果一切正常或者在处理过程中出现异常都会返回true表示允许文件访问。
这段代码的目的是在文件访问时检查权限以确保只有在允许名单中的程序可以访问允许名单中的文件并且不在黑名单中。如果条件不满足它返回false表示拒绝访问。
//验证IPv4地址的访问权限
static bool
allow_ipv4(int sockfd, const struct sockaddr *address, socklen_t addrlen, string return_ip) noexcept(true)
{try {char buf[INET_ADDRSTRLEN];// 强制将传入的地址指针转换为IPv4地址结构struct sockaddr_in *addr_in (struct sockaddr_in *)address;// 检查地址结构长度以及地址类型是否为IPv4if (addrlen sizeof(sockaddr_in) ||addr_in-sin_family ! AF_INET)return true;// 将IPv4地址转换为可读的字符串形式if (inet_ntop(AF_INET, addr_in-sin_addr, buf, sizeof(buf)) NULL)return true;// 将IPv4地址字符串赋值给字符串变量ipconst string ip string(buf);return_ip ip;// 获取当前进程的可执行文件路径const string exe get_real_exe_by_pid(getpid());// 遍历IP地址黑名单检查是否有IP在黑名单中for (auto i : ip_black_list()){if (i.first ip){// 如果IP在黑名单中检查程序是否在黑名单中if (i.second.count(exe) 0)return false;}}// 遍历IP地址白名单检查是否有IP在白名单中for (auto i : ip_white_list()){if (i.first ip){// 如果IP在白名单中检查程序是否不在白名单中if (i.second.count(exe) 0)return false;}}// IP地址访问权限通过返回truereturn true;}catch (...){// 捕获异常如果出现异常也返回truereturn true;}
}// 检查地址结构长度以及地址类型是否为IPv4
if (addrlen sizeof(sockaddr_in) || addr_in-sin_family ! AF_INET)return true;这段代码的目的是在处理套接字地址之前确保传入的地址是有效的 IPv4 地址并且包含足够的信息来处理以避免内存越界错误或无效的操作。如果传入的地址不满足这些条件函数将立即返回 true表示允许访问不进行限制。这有助于确保代码的稳定性和安全性。
typedef int (*open_func_t)(const char *, int, ...); // 定义一个函数指针类型用于指向与标准 open 函数具有相同参数和返回类型的函数
int
open(const char *path, int flags, ...) // 重载标准 open 函数用于拦截文件操作
{static open_func_t old_open NULL; // 静态函数指针用于存储原始的 open 函数地址if (old_open NULL) // 如果第一次调用这个函数old_open (open_func_t)dlsym(RTLD_NEXT, open); // 使用 dlsym 获取标准 open 函数的地址mode_t mode 0; // 定义文件操作的模式if (flags O_CREAT) // 如果传入的 flags 参数包含 O_CREAT 标志表示在文件不存在时创建文件{va_list args; // 定义可变参数列表va_start(args, flags); // 初始化可变参数列表mode va_arg(args, mode_t); // 获取可变参数列表中的 mode_t 参数值va_end(args); // 清理可变参数列表}std::string full_path; // 用于存储文件的完整路径if (allow_open(get_real_exe_by_pid(getpid()).c_str(), path, full_path)) // 调用 allow_open 函数检查是否允许打开文件{log(RESULT::ALLOW, TYPE::FILE, full_path, get_real_exe_by_pid(getpid()), getpid()); // 记录允许的日志return old_open(path, flags, mode); // 调用原始的 open 函数}else{log(RESULT::DENY, TYPE::FILE, full_path, get_real_exe_by_pid(getpid()), getpid()); // 记录拒绝的日志errno EPERM; // 设置 errno 为 EPERM权限错误return -1; // 返回 -1表示打开文件失败}
}
这段代码是一个重载的open函数它用于拦截文件操作并在操作之前进行访问控制。以下是对这段代码的解释 typedef int (*open_func_t)(const char *, int, ...);这行代码定义了一个函数指针类型open_func_t该函数指针可以指向与标准open函数具有相同参数和返回类型的函数。 static open_func_t old_open NULL;这行代码定义了一个静态函数指针old_open并将其初始化为NULL。这个指针将用于保存原始的open函数的地址。 if (old_open NULL)这是一个条件语句用于检查old_open是否为空如果为空表示第一次调用这个函数。 old_open (open_func_t)dlsym(RTLD_NEXT, open);在第一次调用时这行代码使用dlsym函数获取标准open函数的地址并将其存储在old_open函数指针中。这是为了能够在拦截函数中调用原始的open函数。 mode_t mode 0;这行代码定义了一个mode_t类型的变量mode并将其初始化为0。 if (flags O_CREAT)这行代码检查传入的flags参数是否包含O_CREAT标志。O_CREAT标志表示在文件不存在时创建文件。如果flags包含O_CREAT则进入条件块否则跳过。 va_list args; va_start(args, flags); mode va_arg(args, mode_t); va_end(args);在包含O_CREAT标志的情况下这部分代码使用可变参数列表来提取额外的参数。特别是它使用va_list来存储可变参数通过va_start来初始化列表然后使用va_arg来获取参数的值在这种情况下获取了mode的值最后通过va_end来清理列表。 std::string full_path;这行代码定义了一个std::string类型的变量full_path用于存储文件的完整路径。 if (allow_open(get_real_exe_by_pid(getpid()).c_str(), path, full_path))这行代码调用allow_open函数来检查是否允许打开文件。它传递了三个参数当前进程的可执行文件路径、传入的文件路径path以及用于存储文件的完整路径的full_path。如果allow_open函数返回true表示允许打开文件那么它记录了一个允许的日志并通过old_open调用原始的open函数。 else如果allow_open函数返回false表示拒绝打开文件这行代码记录了一个拒绝的日志并设置了errno为EPERM表示权限错误然后返回-1表示打开文件失败。
总之这段代码的目的是拦截标准open函数的调用并在打开文件之前进行访问控制。如果满足控制条件它将允许打开文件并调用原始的open函数。如果不满足条件它将拒绝打开文件并返回一个错误。这有助于实施文件访问控制策略。
typedef int (*openat_func_t)(int fd, const char *, int, ...);
int openat(int fd, const char *path, int flags, ...)
{// 定义一个指向函数指针的变量 old_openat并初始化为 NULLstatic openat_func_t old_openat NULL;// 第一次调用时通过 dlsym 函数获取真正的 openat 函数的地址if (old_openat NULL)old_openat (openat_func_t)dlsym(RTLD_NEXT, openat);// 定义文件打开权限 mode 为 0mode_t mode 0;// 如果 flags 包含 O_CREAT 标志获取变长参数列表中的文件权限 modeif (flags O_CREAT){va_list args;va_start(args, flags);mode va_arg(args, mode_t);va_end(args);}std::string full_path; // 用于存储实际文件的全路径// 调用 allow_open 函数来检查文件的访问权限if (allow_open(get_real_exe_by_pid(getpid()).c_str(), path, full_path)){// 如果允许打开文件记录相应的日志并调用真正的 openat 函数log(RESULT::ALLOW, TYPE::FILE, full_path, get_real_exe_by_pid(getpid()), getpid());return old_openat(fd, path, flags, mode);}else{// 如果不允许打开文件记录相应的日志设置错误号 errno 为 EPERM并返回 -1 表示打开失败log(RESULT::DENY, TYPE::FILE, full_path, get_real_exe_by_pid(getpid()), getpid());errno EPERM;return -1;}
}tool.cpp
string
get_real_path (const char *path) // 函数名及参数说明
{char buf[PATH_MAX 1]; // 声明一个字符数组用于存储路径if (realpath (path, buf) NULL) // 调用realpath函数将传入的路径规范化为绝对路径return path; // 如果realpath调用失败返回原始路径return buf; // 如果realpath调用成功返回规范化后的绝对路径
}string get_remote_ip_by_fd(int sockfd) noexcept(false)
{struct sockaddr_storage addr; // 声明一个套接字地址结构体用于存储远程主机的地址信息socklen_t addrlen sizeof(addr); // 声明并初始化地址结构体的长度变量// 获取远程主机的地址信息并存储到 addr 结构体中如果失败则抛出异常if (getpeername(sockfd, (sockaddr *)addr, addrlen) -1)throw SocketException();sockaddr_in *tcp_addr (sockaddr_in *)addr; // 将 addr 转换为 IPv4 地址结构体char ip[INET_ADDRSTRLEN]; // 用于存储 IP 地址的字符数组// 将二进制形式的 IPv4 地址转换为文本形式的 IP 地址并存储到 ip 数组中if (inet_ntop(AF_INET, tcp_addr-sin_addr, ip, sizeof(ip)) NULL)throw SocketException();return string(ip); // 将 IP 地址转换为 C 字符串并返回
}string get_real_exe_by_pid(pid_t pid) // 函数签名获取指定进程的可执行文件路径
{string buf format(/proc/%d/exe, pid); // 构造进程的符号链接路径如 /proc/1234/exechar exe[PATH_MAX]; // 创建一个字符数组用于存储符号链接指向的可执行文件路径ssize_t nread readlink(buf.c_str(), exe, sizeof(exe) - 1); // 通过 readlink 函数获取符号链接指向的路径if (nread -1) // 如果读取失败返回值为-1说明可能进程不存在或者没有符号链接return string(); // 返回一个空字符串表示获取可执行文件路径失败exe[nread] \0; // 在字符数组的结尾添加 null 终止符以确保它是一个以 null 结尾的 C 字符串return get_real_path(exe); // 调用函数 get_real_path以获取真实的路径并返回
}string get_absolute_path(const char *path) // 函数签名用于获取绝对路径
{if (path NULL || path[0] /) // 如果输入路径为空或已经是绝对路径直接返回原路径return path;char buf[PATH_MAX 1]; // 创建一个字符数组用于存储当前工作目录路径if (getcwd(buf, sizeof(buf) - 1) NULL) // 通过 getcwd 函数获取当前工作目录路径return string(); // 如果获取失败返回一个空字符串表示无法获得绝对路径return string(buf) / path; // 构造并返回合并后的绝对路径将当前工作目录和输入路径拼接
}vectorpid_t get_all_pids() // 函数签名用于获取系统中所有的进程ID
{vectorpid_t pids; // 创建一个存储进程ID的向量DIR *p_dir opendir(/proc); // 打开位于 /proc 目录下的目录流该目录通常包含进程信息if (p_dir NULL) // 如果目录流打开失败return pids; // 返回一个空向量表示无法获取进程IDfor (;;){dirent *p_file readdir(p_dir); // 读取目录中的下一个条目if (p_file NULL) // 如果没有更多的条目可读break; // 退出循环pid_t pid; // 创建一个变量用于存储进程IDif (p_file-d_type DT_DIR (pid atoi(p_file-d_name)) ! 0) // 如果是目录且目录名能够转换为有效的进程IDpids.push_back(pid); // 将该进程ID添加到向量中}closedir(p_dir); // 关闭目录流释放资源return pids; // 返回包含所有有效进程ID的向量
}ptrace_tool.cpp
//从目标进程的内存中读取64位整数数据WORD类型
[[deprecated]]static void
get_tracee_words (pid_t pid, WORD *src, WORD *dest, size_t len) noexcept (false)
{for (size_t i 0; i len; i) {dest[i] ptrace (PTRACE_PEEKDATA, pid, src i, NULL);if (dest[i] -1)throw PtraceException ();}
}它接受参数 pid要读取的数据的地址 src以及存储读取数据的数组 dest 以及要读取的数据的长度 len。使用 ptrace 调用的 PTRACE_PEEKDATA 选项从目标进程的内存中读取数据并将数据存储在 dest 数组中。如果读取失败会抛出 PtraceException 异常。 PTRACE_POKEDATA 是 Linux 操作系统提供的 ptrace 系统调用的一个选项之一它用于将数据写入远程进程的内存空间。这个选项通常用于修改目标进程的内存中的数据允许一个进程追踪和修改另一个进程的执行状态和内存。 具体解释如下 ptrace 系统调用 ptrace 是一个用于进程追踪的系统调用允许一个进程通常是父进程监视和控制另一个进程。通过 ptrace一个进程可以读取和写入目标进程的寄存器、内存以及控制目标进程的执行。 PTRACE_POKEDATA PTRACE_POKEDATA 是 ptrace 的一个选项请求它表示要将数据写入目标进程的内存。这个选项通常与 ptrace 函数一起使用用于修改目标进程的内存。 用途 PTRACE_POKEDATA 主要用于在调试或进程注入等场景中以编程方式修改目标进程的内存数据。例如可以使用它来修改目标进程的变量值注入代码或执行其他需要改变内存数据的操作。 参数 调用 ptrace 时需要提供目标进程的进程 ID要写入的目标地址以及要写入的数据。 注意事项 使用 PTRACE_POKEDATA 需要特权通常只能由具有足够权限的进程来执行例如需要具有 root 或 debug 能力的权限。
总之PTRACE_POKEDATA 是 ptrace 的一个选项用于在目标进程的内存中写入数据通常用于调试、注入或修改目标进程的内存。
//向目标进程的内存中写入64位整数数据
[[deprecated]]static void
set_tracee_words (pid_t pid, WORD *src, WORD *dest, size_t len)
{for (size_t i 0; i len; i) {if (ptrace (PTRACE_POKEDATA, pid, dest[i], src[i]) -1)throw PtraceException ();}
}它接受参数 pid要写入的数据 src以及要写入的目标地址 dest 以及数据的长度 len。使用 ptrace 调用的 PTRACE_POKEDATA 选项将数据写入目标进程的内存。如果写入失败会抛出 PtraceException 异常。
//计算目标进程内存中以remote_str开头的字符串的长度
int get_tracee_strlen (pid_t pid, char *remote_str)
{user_regs_struct regs;int len 0;for (int i 0; ; i) {WORD word ptrace (PTRACE_PEEKDATA, pid, remote_str i * sizeof (WORD), NULL);if (word -1)throw PtraceException ();char *end (char *) memchr (word, \0, sizeof (word));if (end NULL)len sizeof (WORD) / sizeof (char);else {len end - (char *) word;break;}}return len;
}它接受参数 pid 和 remote_str表示目标进程的进程 ID 和指向目标进程内存的指针。通过循环逐个读取字符并查找字符串的终止符 \0 来计算字符串的长度。如果读取失败会抛出 PtraceException 异常。
//从目标进程的内存中复制字符串并返回复制后的字符串
char * get_tracee_strdup (pid_t pid, char *remote_str)
{const int len get_tracee_strlen (pid, remote_str);char buf[len 1];get_tracee_bytes (pid, remote_str, buf, len 1);return strdup (buf);
}此函数用于从目标进程的内存中复制字符串并返回复制后的字符串。它首先调用 get_tracee_strlen 计算字符串的长度然后调用 get_tracee_bytes 读取字符串的字节数据。最后它使用 strdup 函数分配新的字符串并返回。
// 从目标进程的内存中读取字节数据
void get_tracee_bytes (pid_t pid, void *remote_src, void *local_dest, size_t len)
{const int word_len len / sizeof (WORD);for (size_t i 0; i word_len ; i) {WORD ret ptrace (PTRACE_PEEKDATA, pid, (WORD *) remote_src i, NULL);if (ret -1)throw PtraceException ();( (WORD *) local_dest) [i] ret;}if (word_len 1) {void *dest_last_word (WORD *) ( (char *) local_dest len) - 1;void *src_last_word (WORD *) ( (char *) remote_src len) - 1;if (len % sizeof (WORD) ! 0) {WORD ret ptrace (PTRACE_PEEKDATA, pid, src_last_word, NULL);if (ret -1)throw PtraceException ();* (WORD *) dest_last_word ret;}}
}它接受参数 pid指向目标进程内存的指针 remote_src以及存储读取数据的本地缓冲区 local_dest 以及要读取的数据的长度 len。该函数首先将数据以 64 位整数WORD的形式逐个字的方式读取然后将它们存储在 local_dest 中。最后如果 len 不是 WORD 大小的整数倍它会单独读取最后一个字。
// 从目标进程的内存中读取字节数据
void set_tracee_bytes (pid_t pid, void *remote_src, void *local_dest, size_t len)
{const int word_len len / sizeof (WORD);for (size_t i 0; i word_len ; i) {if (ptrace (PTRACE_POKEDATA, pid, (WORD *) local_dest i, * ( (WORD *) remote_src i)) -1)throw PtraceException ();}if (word_len 1) {void *dest_last_word (WORD *) ( (char *) local_dest len) - 1;void *src_last_word (WORD *) ( (char *) remote_src len) - 1;if (len % sizeof (WORD) ! 0) {if (ptrace (PTRACE_POKEDATA, pid, dest_last_word, * (WORD *) src_last_word) -1)throw PtraceException ();}}
}它的参数和操作方式与 get_tracee_bytes 函数类似但是它用 ptrace 调用的 PTRACE_POKEDATA 选项将数据写入目标进程的内存。
总结
inet_ntop
inet_ntop 函数用于将二进制形式的网络地址转换为人类可读的IPv4或IPv6地址表示。
其原型如下
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af地址族Address Family可以是 AF_INETIPv4或 AF_INET6IPv6。src指向要转换的二进制网络地址的指针。dst指向用于存储结果的缓冲区。sizedst 缓冲区的大小。
inet_ntop 函数的原理是将二进制网络地址转换为可读的点分十进制IPv4或冒号十六进制IPv6表示。函数根据给定的地址族 af采用不同的格式化方式来完成这一转换。
对于 IPv4 地址af 为 AF_INETinet_ntop 函数将 32 位的二进制地址拆分成四个 8 位的部分然后将它们以点分十进制的形式表示。例如二进制地址 0x7F000001 被转换为字符串 “127.0.0.1”。
对于 IPv6 地址af 为 AF_INET6inet_ntop 函数将 128 位的二进制地址按照冒号十六进制表示。例如IPv6 地址 2001:0db8:85a3:0000:0000:8a2e:0370:7334 保持不变。
dlsym
dlsym 函数是动态链接库操作的一部分用于在共享库中查找符号函数或变量。其原型如下
void *dlsym(void *handle, const char *symbol);handle表示已经打开的动态链接库的句柄通常是由 dlopen 函数返回的。symbol是你想查找的符号函数或变量的名称。
dlsym 函数的工作原理涉及以下几个步骤
dlsym 函数通过 handle 参数确定要在哪个已加载的共享库中查找符号。它会在给定的共享库中查找具有名称 symbol 的符号。如果找到符号dlsym 返回指向该符号的指针函数指针或变量指针否则返回 NULL。
这个函数的主要用途是在运行时从共享库中获取函数或变量的地址以便在程序中调用或使用它们。这是一种动态加载共享库中的函数的方法可以在程序运行时决定使用哪个共享库并且可以根据需要加载或卸载这些库。
va_start
va_start 函数是 C/C 标准库中的一个宏用于在函数内部访问可变参数列表variable argument list。它的原型通常定义在 cstdarg 或 stdarg.h 头文件中但是 va_start 宏的具体实现会根据编译器和平台而有所不同。
va_start 宏的一般原型如下
void va_start(va_list ap, last_arg);va_list ap 是一个指向可变参数列表的指针它将在函数内部用于迭代访问参数。last_arg 是可变参数列表中的最后一个固定参数用于确定可变参数列表的起始位置。
va_start 宏的原理是基于编译器和体系结构的底层机制通常使用汇编代码来实现。它的主要任务是将 va_list 指针初始化为指向参数列表中的第一个可变参数。
具体实现方法取决于编译器和平台但通常涉及以下步骤 确定固定参数的位置。编译器需要知道在参数列表中哪里是可变参数的开始位置。这通常由 last_arg 参数指定。 计算可变参数列表的地址。编译器会使用一些规则来计算可变参数列表的地址。这通常涉及堆栈指针栈帧指针的调整和偏移计算。 初始化 va_list 指针。va_start 宏会将 va_list 指针初始化为可变参数列表的起始位置以便函数内部可以使用 va_arg 宏来访问参数。
总之va_start 宏的原理是在函数内部为可变参数列表创建一个指针使得程序可以依次访问参数。不同编译器和平台的实现可能会有所不同但通常都是基于底层的堆栈和内存管理机制。
va_arg
va_arg 函数是 C/C 标准库中用于访问可变参数列表variable argument list的宏。它的原型通常定义在 cstdarg 或 stdarg.h 头文件中但 va_arg 宏的具体实现会根据编译器和平台而有所不同。
一般情况下va_arg 宏的原型如下
type va_arg(va_list ap, type);va_list ap 是一个指向可变参数列表的指针它在 va_start 函数之后初始化用于迭代访问参数。type 是要获取的参数的类型。
va_arg 宏的原理是基于编译器和体系结构的底层机制通常使用汇编代码来实现。它的主要任务是从可变参数列表中按指定类型提取参数的值。
具体实现方法取决于编译器和平台但通常涉及以下步骤 计算参数的大小。编译器需要知道参数的大小以便正确地从堆栈中读取数据。这取决于参数的类型。 更新 va_list 指针。va_arg 宏会将 va_list 指针移动到下一个参数的位置以准备下一次调用 va_arg。 从内存中读取参数值。va_arg 宏通过 va_list 指针获取参数的值然后将指针移动到下一个参数的位置。
总之va_arg 宏的原理是在可变参数列表中按照指定的类型提取参数值。不同编译器和平台的实现可能会有所不同但通常都是基于底层的堆栈和内存管理机制。 va_arg 宏为处理可变参数提供了一种通用的方法使得在不知道参数个数和类型的情况下能够访问参数。
va_end
va_end 函数是 C/C 标准库中用于终止可变参数列表variable argument list操作的宏。它的原型通常定义在 cstdarg 或 stdarg.h 头文件中但 va_end 宏的具体实现会根据编译器和平台而有所不同。
一般情况下va_end 宏的原型如下
void va_end(va_list ap);va_list ap 是一个指向可变参数列表的指针它在 va_start 函数之后初始化。
va_end 宏的原理是用于清理 va_list 指针以便资源得到正确释放。具体实现方法取决于编译器和平台但通常会在 va_end 中执行以下操作 将 va_list 指针设置为一个未定义或无效的状态。这意味着该指针不再指向可变参数列表中的任何参数。 释放或清理 va_list 指针可能使用的任何资源。这通常涉及一些与堆栈或寄存器状态相关的操作以确保不会出现内存泄漏或资源泄漏。
总之va_end 宏的原理是用于清理和终止可变参数列表的操作以确保不会出现资源泄漏或其他问题。不同编译器和平台的实现方式可能有所不同但它们的共同目标是安全地结束可变参数列表的操作。
sendto
sendto 函数是用于将数据发送到指定的目标地址的系统调用通常用于网络编程。它的原型如下
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);sockfd 是套接字文件描述符用于标识要发送数据的套接字。buf 是包含要发送的数据的缓冲区。len 是要发送的数据的字节数。flags 是发送选项通常可以设置为 0。dest_addr 是目标地址的指针通常是 struct sockaddr 结构的指针包含目标地址信息。addrlen 是目标地址结构的长度。
sendto 函数的原理是将 buf 中的数据发送到指定的目标地址然后返回发送的字节数。数据包将通过 sockfd 套接字发送而 dest_addr 和 addrlen 用于指定目标地址。
原理包括以下步骤
根据 sockfd 找到关联的套接字该套接字用于数据发送。将 buf 缓冲区中的数据封装成数据包同时添加目标地址信息由 dest_addr 指定。数据包被发送到目标地址这通常涉及到网络协议栈的操作。sendto 函数返回发送的字节数或出现的错误。
sendto 可以用于 UDP 和基于 IP 的协议例如 ICMP以便将数据发送到指定的目标地址。通过 dest_addr 参数您可以指定数据包要发送到的目标主机和端口。此函数常用于网络编程中用于实现数据的发送和接收。
realpath
realpath 函数用于获取一个路径的绝对路径将相对路径转换为绝对路径。其原型如下
char *realpath(const char *path, char *resolved_path);path 是要获取绝对路径的输入路径。resolved_path 是一个缓冲区用于存储解析后的绝对路径。它可以为 NULL如果为 NULLrealpath 函数会自动为您分配内存。
realpath 函数的原理是将输入的相对路径 path 转换为绝对路径并存储在 resolved_path 缓冲区中。如果 resolved_path 参数为 NULL则会自动分配内存并存储绝对路径。
原理包括以下步骤
realpath 检查输入路径 path 是否为相对路径或绝对路径。如果 path 为绝对路径以 / 开头则它是其自身的绝对路径无需进一步处理。如果 path 为相对路径realpath 将获取当前工作目录并将其与 path 连接以得到绝对路径。realpath 对路径中的符号链接进行解析以获得路径的最终绝对路径。这包括将路径中的 . 和 .. 等符号链接替换为实际目录。最终的绝对路径存储在 resolved_path 缓冲区中或者如果 resolved_path 为 NULL则由 realpath 函数自动分配内存来存储绝对路径。realpath 返回指向 resolved_path 缓冲区的指针其中包含了输入路径的绝对路径。这个缓冲区可以被后续代码使用。
realpath 函数通常用于获取文件的绝对路径以确保以绝对路径方式引用文件。这在文件系统操作和路径处理中非常有用可以避免相对路径引发的问题。
getpeername
getpeername 函数用于获取与已连接套接字关联的远程端的地址信息。其原型如下
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd 是一个已连接套接字的文件描述符用于表示与远程主机建立的连接。addr 是一个指向 struct sockaddr 结构的指针用于存储远程端的地址信息。addrlen 是一个指向 socklen_t 类型的指针用于存储 addr 缓冲区的长度。
getpeername 函数的原理是在套接字 sockfd 上执行操作以获取远程端的地址信息并存储在 addr 缓冲区中。
原理包括以下步骤
getpeername 函数接收 sockfd这是一个已连接套接字表示与远程主机的连接。它将 addr 缓冲区用于存储远程端的地址信息。通过 addrlen 参数传递缓冲区的大小。getpeername 函数将远程端的地址信息填充到 addr 缓冲区中。如果成功函数返回0否则返回-1并在错误情况下设置 errno以指示错误的类型。
getpeername 函数通常用于网络编程以获取远程客户端的地址信息以便了解与服务器建立连接的客户端。这是在服务器端套接字编程中的常见用例用于识别连接到服务器的客户端的地址信息。
inet_ntop
inet_ntop 函数用于将网络字节序的 IP 地址转换为人类可读的 IP 地址字符串。其原型如下
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af 是地址族参数通常是 AF_INET 表示 IPv4 地址族或 AF_INET6 表示 IPv6 地址族。src 是指向包含二进制 IP 地址的内存地址。dst 是用于存储 IP 地址字符串的缓冲区。size 是 dst 缓冲区的大小。
原理 inet_ntop 函数的实现依赖于不同的操作系统但其核心思想相似。它将二进制 IP 地址转换为字符串的过程通常如下 首先函数根据地址族 (af) 来判断是 IPv4 还是 IPv6 地址。 然后它根据地址族决定如何将二进制地址解析成字符串。对于 IPv4 地址将四个 8 位整数以点分隔的形式拼接成字符串对于 IPv6 地址将 16 位整数以冒号分隔的形式拼接成字符串。 函数将转换后的字符串复制到 dst 缓冲区中同时确保不会超过 size 字节的长度。 如果转换成功函数返回指向 dst 缓冲区的指针否则返回 NULL并在 errno 中设置相应的错误码如 EINVAL无效的地址族或 ENOSPC缓冲区不足。
需要注意的是inet_ntop 是将二进制地址转换为可读字符串的逆过程与之相反的函数是 inet_pton它将字符串转换为二进制地址。这两个函数在网络编程中用于进行 IP 地址的解析和构建。
readlink
readlink 函数用于读取符号链接的目标路径。它的原型如下
ssize_t readlink(const char *path, char *buf, size_t bufsiz);path 是要读取的符号链接文件的路径。buf 是一个字符数组用于存储目标路径。bufsiz 是 buf 的大小用于指定目标路径的最大长度。
原理 readlink 函数的目的是获取符号链接文件所指向的实际目标路径。其工作原理如下 当调用 readlink 函数时它会打开 path 所指定的符号链接文件并尝试读取链接文件的内容。 如果成功它将读取的内容存储在 buf 中并返回所复制的字符数不包括 null 终止字符。如果 bufsiz 大于实际目标路径的长度buf 中的数据将以 null 终止。 如果读取的内容超过了 buf 的容量bufsiz 不足以容纳目标路径readlink 函数会截断目标路径并返回截断后的字符数。此时buf 中的数据仍以 null 终止。 如果读取符号链接文件失败readlink 函数返回 -1并在 errno 中设置相应的错误码如 EACCES权限不足或 ENOENT文件不存在。
需要注意的是readlink 仅适用于符号链接文件。对于硬链接或普通文件它不起作用。符号链接是一种特殊类型的文件其中包含对其他文件或目录的路径引用因此读取它的内容通常是获取所引用文件的路径。
opendir
opendir 函数用于打开指定目录并返回一个指向目录流DIR 结构体的指针的句柄以便后续对目录中的文件和子目录进行遍历。其原型如下
DIR *opendir(const char *dirname);dirname 是一个字符串表示要打开的目录的路径名。
原理 当调用 opendir 函数时它会尝试打开指定路径的目录。 如果成功opendir 返回一个指向 DIR 结构体的指针该结构体用于表示打开的目录流。这个结构体包含有关目录的信息如目录的文件描述符和目录项列表。 目录流句柄可以用于后续的目录遍历操作例如使用 readdir 函数读取目录中的文件和子目录项。 如果打开目录失败opendir 返回 NULL表示出现了错误。在这种情况下通常可以使用 errno 来获取出错的详细信息例如 ENOENT 表示指定的目录不存在EACCES 表示无权限访问目录等。
opendir 和 readdir 函数通常用于目录遍历操作允许程序在目录中查找文件和子目录并对它们进行处理。这对于编写文件管理和操作系统相关的程序非常有用。
readdir
readdir 函数用于从已打开的目录流中读取下一个目录项。其原型如下
struct dirent *readdir(DIR *dirp);dirp 是一个指向已打开目录流DIR 结构体的指针的句柄。 struct dirent 是一个结构体表示目录中的一个项包括文件名、文件类型和其他属性。
原理 readdir 函数从指定的目录流 dirp 中读取下一个目录项并返回一个指向 struct dirent 结构体的指针其中包含有关该目录项的信息。 如果目录流已经到达末尾即没有更多的目录项可读取或者出现错误readdir 返回 NULL表示读取结束或出现错误。此时通常可以使用 errno 来获取出错的详细信息。 通过多次调用 readdir 函数可以依次读取目录中的所有文件和子目录项直到读取到末尾为止。 struct dirent 结构体中包含了目录项的信息如文件名、文件类型和其他属性。您可以使用这些信息来进一步处理目录中的文件和子目录。
readdir 函数通常与 opendir 函数一起使用用于在目录中遍历文件和子目录。这对于需要执行文件管理或目录操作的应用程序非常有用。
atoi
atoi 函数用于将字符串转换为整数int。其原型如下
int atoi(const char *str);str 是一个指向包含表示整数的字符串的指针。
原理 atoi 函数从字符串 str 的起始位置开始扫描并跳过前导空白字符如空格、制表符等。 一旦遇到非空白字符atoi 将开始解析整数。它会继续读取字符直到遇到非数字字符或字符串的末尾。 解析期间atoi 将读取的字符转换为整数并将其积累到一个整数值中。该整数值的初始值为零。 如果字符串中包含无效字符或字符串为空atoi 将停止解析并返回当前积累的整数值。 如果整数超出了 int 数据类型的范围结果是未定义的。 返回值为解析后的整数值。
atoi 主要用于将字符串形式的数字转换为整数常用于文本处理和输入转换但不提供错误检测机制。如果需要更强大的字符串到整数的转换和错误处理可以使用 strtol 函数或其他更安全的替代方法。