ip_file_Hook項目解讀

程序流程

執行文件訪問攔截和 IP 地址攔截的流程:

文件訪問攔截功能:

  1. 當應用程序嘗試執行文件操作,例如打開文件,調用的是 openopenat 函數。

  2. 由于這兩個函數已經被重定向為自定義的版本,所以實際上調用的是 openopenat 函數的自定義替代版本。

  3. 自定義的 openopenat 函數首先檢查傳遞的文件路徑和執行文件操作的進程是否符合訪問控制策略。

  4. 它檢查文件路徑是否在黑名單或白名單中,并檢查執行文件操作的進程是否在白名單中。如果文件或進程不符合策略,函數會拒絕文件操作。

  5. 如果文件和進程都符合策略,它會記錄文件訪問操作的結果,然后調用真正的 openopenat 函數執行操作。

  6. 如果文件或進程不符合策略,它會記錄拒絕的結果,并設置 errnoEPERM,表示權限被拒絕。

IP 地址攔截功能:

  1. 當應用程序嘗試執行網絡連接操作,例如使用 connect 函數或發送數據到遠程主機,調用的是 connectsendto 函數。

  2. 與文件訪問攔截類似,這兩個函數也已被重定向為自定義版本,因此實際上調用的是自定義的 connectsendto 函數。

  3. 自定義的 connectsendto 函數首先檢查傳遞的目標 IP 地址以及執行連接或數據發送操作的進程是否符合訪問控制策略。

  4. 它檢查 IP 地址是否在黑名單或白名單中,并檢查執行操作的進程是否在白名單中。如果 IP 地址或進程不符合策略,函數會拒絕操作。

  5. 如果 IP 地址和進程都符合策略,它會記錄 IP 操作的結果,然后調用真正的 connectsendto 函數執行操作。

  6. 如果 IP 地址或進程不符合策略,它會記錄拒絕的結果,并設置 errnoEPERM,表示權限被拒絕。

總的來說,這兩段代碼的流程是先檢查文件或 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風格字符串(exepath)以及一個字符串引用參數(return_full_path)。
  • 函數首先檢查傳入的exepath是否為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,表示拒絕打開文件,這行代碼記錄了一個拒絕的日志,并設置了errnoEPERM(表示權限錯誤),然后返回-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/exe"char 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; // 構造并返回合并后的絕對路徑,將當前工作目錄和輸入路徑拼接
}
vector<pid_t> get_all_pids() // 函數簽名,用于獲取系統中所有的進程ID
{vector<pid_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 系統調用的一個選項之一,它用于將數據寫入遠程進程的內存空間。這個選項通常用于修改目標進程的內存中的數據,允許一個進程追蹤和修改另一個進程的執行狀態和內存。

具體解釋如下:

  1. ptrace 系統調用: ptrace 是一個用于進程追蹤的系統調用,允許一個進程(通常是父進程)監視和控制另一個進程。通過 ptrace,一個進程可以讀取和寫入目標進程的寄存器、內存,以及控制目標進程的執行。

  2. PTRACE_POKEDATA: PTRACE_POKEDATAptrace 的一個選項(請求),它表示要將數據寫入目標進程的內存。這個選項通常與 ptrace 函數一起使用,用于修改目標進程的內存。

  3. 用途: PTRACE_POKEDATA 主要用于在調試或進程注入等場景中,以編程方式修改目標進程的內存數據。例如,可以使用它來修改目標進程的變量值,注入代碼,或執行其他需要改變內存數據的操作。

  4. 參數: 調用 ptrace 時,需要提供目標進程的進程 ID,要寫入的目標地址,以及要寫入的數據。

  5. 注意事項: 使用 PTRACE_POKEDATA 需要特權,通常只能由具有足夠權限的進程來執行,例如,需要具有 root 或 debug 能力的權限。

總之,PTRACE_POKEDATAptrace 的一個選項,用于在目標進程的內存中寫入數據,通常用于調試、注入或修改目標進程的內存。

//向目標進程的內存中寫入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;
}
  • 它接受參數 pidremote_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_INET(IPv4)或 AF_INET6(IPv6)。
  • src:指向要轉換的二進制網絡地址的指針。
  • dst:指向用于存儲結果的緩沖區。
  • sizedst 緩沖區的大小。

inet_ntop 函數的原理是將二進制網絡地址轉換為可讀的點分十進制(IPv4)或冒號十六進制(IPv6)表示。函數根據給定的地址族 af,采用不同的格式化方式來完成這一轉換。

對于 IPv4 地址(afAF_INET),inet_ntop 函數將 32 位的二進制地址拆分成四個 8 位的部分,然后將它們以點分十進制的形式表示。例如,二進制地址 0x7F000001 被轉換為字符串 “127.0.0.1”。

對于 IPv6 地址(afAF_INET6),inet_ntop 函數將 128 位的二進制地址按照冒號十六進制表示。例如,IPv6 地址 2001:0db8:85a3:0000:0000:8a2e:0370:7334 保持不變。

dlsym

dlsym 函數是動態鏈接庫操作的一部分,用于在共享庫中查找符號(函數或變量)。其原型如下:

void *dlsym(void *handle, const char *symbol);
  • handle:表示已經打開的動態鏈接庫的句柄,通常是由 dlopen 函數返回的。
  • symbol:是你想查找的符號(函數或變量)的名稱。

dlsym 函數的工作原理涉及以下幾個步驟:

  1. dlsym 函數通過 handle 參數確定要在哪個已加載的共享庫中查找符號。
  2. 它會在給定的共享庫中查找具有名稱 symbol 的符號。
  3. 如果找到符號,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 指針初始化為指向參數列表中的第一個可變參數。

具體實現方法取決于編譯器和平臺,但通常涉及以下步驟:

  1. 確定固定參數的位置。編譯器需要知道在參數列表中哪里是可變參數的開始位置。這通常由 last_arg 參數指定。

  2. 計算可變參數列表的地址。編譯器會使用一些規則來計算可變參數列表的地址。這通常涉及堆棧指針(棧幀指針)的調整和偏移計算。

  3. 初始化 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 宏的原理是基于編譯器和體系結構的底層機制,通常使用匯編代碼來實現。它的主要任務是從可變參數列表中按指定類型提取參數的值。

具體實現方法取決于編譯器和平臺,但通常涉及以下步驟:

  1. 計算參數的大小。編譯器需要知道參數的大小,以便正確地從堆棧中讀取數據。這取決于參數的類型。

  2. 更新 va_list 指針。va_arg 宏會將 va_list 指針移動到下一個參數的位置,以準備下一次調用 va_arg

  3. 從內存中讀取參數值。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 中執行以下操作:

  1. va_list 指針設置為一個未定義或無效的狀態。這意味著該指針不再指向可變參數列表中的任何參數。

  2. 釋放或清理 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_addraddrlen 用于指定目標地址。

原理包括以下步驟:

  1. 根據 sockfd 找到關聯的套接字,該套接字用于數據發送。
  2. buf 緩沖區中的數據封裝成數據包,同時添加目標地址信息(由 dest_addr 指定)。
  3. 數據包被發送到目標地址,這通常涉及到網絡協議棧的操作。
  4. 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,則會自動分配內存并存儲絕對路徑。

原理包括以下步驟:

  1. realpath 檢查輸入路徑 path 是否為相對路徑或絕對路徑。如果 path 為絕對路徑(以 / 開頭),則它是其自身的絕對路徑,無需進一步處理。
  2. 如果 path 為相對路徑,realpath 將獲取當前工作目錄,并將其與 path 連接,以得到絕對路徑。
  3. realpath 對路徑中的符號鏈接進行解析,以獲得路徑的最終絕對路徑。這包括將路徑中的 ... 等符號鏈接替換為實際目錄。
  4. 最終的絕對路徑存儲在 resolved_path 緩沖區中,或者如果 resolved_pathNULL,則由 realpath 函數自動分配內存來存儲絕對路徑。
  5. 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 緩沖區中。

原理包括以下步驟:

  1. getpeername 函數接收 sockfd,這是一個已連接套接字,表示與遠程主機的連接。
  2. 它將 addr 緩沖區用于存儲遠程端的地址信息。
  3. 通過 addrlen 參數傳遞緩沖區的大小。
  4. getpeername 函數將遠程端的地址信息填充到 addr 緩沖區中。
  5. 如果成功,函數返回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 地址字符串的緩沖區。
  • sizedst 緩沖區的大小。

原理:
inet_ntop 函數的實現依賴于不同的操作系統,但其核心思想相似。它將二進制 IP 地址轉換為字符串的過程通常如下:

  1. 首先,函數根據地址族 (af) 來判斷是 IPv4 還是 IPv6 地址。

  2. 然后,它根據地址族決定如何將二進制地址解析成字符串。對于 IPv4 地址,將四個 8 位整數以點分隔的形式拼接成字符串;對于 IPv6 地址,將 16 位整數以冒號分隔的形式拼接成字符串。

  3. 函數將轉換后的字符串復制到 dst 緩沖區中,同時確保不會超過 size 字節的長度。

  4. 如果轉換成功,函數返回指向 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 是一個字符數組,用于存儲目標路徑。
  • bufsizbuf 的大小,用于指定目標路徑的最大長度。

原理:
readlink 函數的目的是獲取符號鏈接文件所指向的實際目標路徑。其工作原理如下:

  1. 當調用 readlink 函數時,它會打開 path 所指定的符號鏈接文件,并嘗試讀取鏈接文件的內容。

  2. 如果成功,它將讀取的內容存儲在 buf 中,并返回所復制的字符數,不包括 null 終止字符。如果 bufsiz 大于實際目標路徑的長度,buf 中的數據將以 null 終止。

  3. 如果讀取的內容超過了 buf 的容量(bufsiz 不足以容納目標路徑),readlink 函數會截斷目標路徑并返回截斷后的字符數。此時,buf 中的數據仍以 null 終止。

  4. 如果讀取符號鏈接文件失敗,readlink 函數返回 -1,并在 errno 中設置相應的錯誤碼,如 EACCES(權限不足)或 ENOENT(文件不存在)。

需要注意的是,readlink 僅適用于符號鏈接文件。對于硬鏈接或普通文件,它不起作用。符號鏈接是一種特殊類型的文件,其中包含對其他文件或目錄的路徑引用,因此讀取它的內容通常是獲取所引用文件的路徑。

opendir

opendir 函數用于打開指定目錄并返回一個指向目錄流(DIR 結構體的指針)的句柄,以便后續對目錄中的文件和子目錄進行遍歷。其原型如下:

DIR *opendir(const char *dirname);
  • dirname 是一個字符串,表示要打開的目錄的路徑名。

原理:

  1. 當調用 opendir 函數時,它會嘗試打開指定路徑的目錄。

  2. 如果成功,opendir 返回一個指向 DIR 結構體的指針,該結構體用于表示打開的目錄流。這個結構體包含有關目錄的信息,如目錄的文件描述符和目錄項列表。

  3. 目錄流句柄可以用于后續的目錄遍歷操作,例如使用 readdir 函數讀取目錄中的文件和子目錄項。

  4. 如果打開目錄失敗,opendir 返回 NULL,表示出現了錯誤。在這種情況下,通常可以使用 errno 來獲取出錯的詳細信息,例如 ENOENT 表示指定的目錄不存在,EACCES 表示無權限訪問目錄等。

opendirreaddir 函數通常用于目錄遍歷操作,允許程序在目錄中查找文件和子目錄,并對它們進行處理。這對于編寫文件管理和操作系統相關的程序非常有用。

readdir

readdir 函數用于從已打開的目錄流中讀取下一個目錄項。其原型如下:

struct dirent *readdir(DIR *dirp);
  • dirp 是一個指向已打開目錄流(DIR 結構體的指針)的句柄。

  • struct dirent 是一個結構體,表示目錄中的一個項,包括文件名、文件類型和其他屬性。

原理:

  1. readdir 函數從指定的目錄流 dirp 中讀取下一個目錄項,并返回一個指向 struct dirent 結構體的指針,其中包含有關該目錄項的信息。

  2. 如果目錄流已經到達末尾(即沒有更多的目錄項可讀取),或者出現錯誤,readdir 返回 NULL,表示讀取結束或出現錯誤。此時,通常可以使用 errno 來獲取出錯的詳細信息。

  3. 通過多次調用 readdir 函數,可以依次讀取目錄中的所有文件和子目錄項,直到讀取到末尾為止。

  4. struct dirent 結構體中包含了目錄項的信息,如文件名、文件類型和其他屬性。您可以使用這些信息來進一步處理目錄中的文件和子目錄。

readdir 函數通常與 opendir 函數一起使用,用于在目錄中遍歷文件和子目錄。這對于需要執行文件管理或目錄操作的應用程序非常有用。

atoi

atoi 函數用于將字符串轉換為整數(int)。其原型如下:

int atoi(const char *str);
  • str 是一個指向包含表示整數的字符串的指針。

原理:

  1. atoi 函數從字符串 str 的起始位置開始掃描,并跳過前導空白字符(如空格、制表符等)。

  2. 一旦遇到非空白字符,atoi 將開始解析整數。它會繼續讀取字符,直到遇到非數字字符或字符串的末尾。

  3. 解析期間,atoi 將讀取的字符轉換為整數,并將其積累到一個整數值中。該整數值的初始值為零。

  4. 如果字符串中包含無效字符或字符串為空,atoi 將停止解析,并返回當前積累的整數值。

  5. 如果整數超出了 int 數據類型的范圍,結果是未定義的。

  6. 返回值為解析后的整數值。

atoi 主要用于將字符串形式的數字轉換為整數,常用于文本處理和輸入轉換,但不提供錯誤檢測機制。如果需要更強大的字符串到整數的轉換和錯誤處理,可以使用 strtol 函數或其他更安全的替代方法。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/163665.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/163665.shtml
英文地址,請注明出處:http://en.pswp.cn/news/163665.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 Flink SQL 和 Paimon 構建流式湖倉新方案

本文整理自阿里云智能開源表存儲負責人&#xff0c;Founder of Paimon&#xff0c;Flink PMC 成員李勁松在云棲大會開源大數據專場的分享。本篇內容主要分為四部分&#xff1a; 數據分析架構演進介紹 Apache PaimonFlink Paimon 流式湖倉流式湖倉Demo演示 數據分析架構演進 …

蝦皮數據參謀:知蝦助力商家實現數據化運營的利器

在如今競爭激烈的電商市場中&#xff0c;商家需要準確的數據分析來指導他們的業務決策。Shopee電商平臺的數據分析工具——蝦皮數據參謀&#xff08;知蝦&#xff09;&#xff0c;為商家提供了豐富的數據分析服務&#xff0c;包括商品市場、銷量、價格分布、物流監控、差評監控…

ArkTS聲明式開發范式

裝飾器 用來裝飾類、結構體、方法以及變量&#xff0c;賦予其特殊的含義&#xff0c;如上述示例中 Entry 、 Component 、 State 都是裝飾器。 Component 表示這是個自定義組件&#xff1b; Entry 則表示這是個入口組件&#xff1b; State 表示組件中的狀態變量&#xff0c;…

最新版靈沐V3.3微信資源類小程序源碼支持流量主

源碼簡介 最新版靈沐V3.3微信資源類小程序源碼支持流量主&#xff0c;一套不錯的流量主變現資源下載小程序&#xff0c;它支持在微信、QQ和抖音平臺上運行。這次更新主要集中在全局UI設計的升級&#xff0c;并依然注重資源下載和激勵視頻變現的功能。另外&#xff0c;還新增了…

VR模擬仿真技術為司法科普建設注入更多的智慧和力量

虛擬現實(VR)技術已經逐漸滲透到各個領域&#xff0c;包括司法領域&#xff0c;在法學院教學中&#xff0c;VR虛擬現實和web3d開發技術的興起&#xff0c;讓司法教育也突破傳統教授式、演練式的教學模式&#xff0c;通過VR特有的沉浸式展示特點&#xff0c;實現了真實法庭效果的…

【Sorted Set】Redis常用數據類型: ZSet [使用手冊]

個人簡介&#xff1a;Java領域新星創作者&#xff1b;阿里云技術博主、星級博主、專家博主&#xff1b;正在Java學習的路上摸爬滾打&#xff0c;記錄學習的過程~ 個人主頁&#xff1a;.29.的博客 學習社區&#xff1a;進去逛一逛~ 目錄 ⑤Redis Zset 操作命令匯總1. zadd 添加或…

【考研數據結構代碼題7】求一元多項式之和

題目&#xff1a;編寫一個算法&#xff0c;求一元多項式之和 考綱&#xff1a;一元多項式的表示與相加 題型&#xff1a;代碼填空或算法設計 難度&#xff1a;★★★ 參考代碼 typedef struct node{float coef;//系數int exp;//次數struct node *next; }polynode; polynode *…

5 分鐘,開發自己的 AI 文檔助手!手把手教程

大家好&#xff0c;我是魚皮。 幾個月前&#xff0c;我自己開發過一個 AI 文檔總結助手應用。給大家簡單演示一下&#xff0c;首先我上傳了一個文檔&#xff0c;定義 1 1 等于 3&#xff1a; 然后把文檔喂給 AI 文檔總結助手&#xff0c;再向它提問&#xff0c;然后 AI 就回答…

登陸頁面模板

簡單好看的登陸頁面 vue項目代碼 可忽略js部分 先來個效果圖 <template><div class"login"><div class"content"><p >賬戶密碼登錄</p><div class"unit"><label class"label">用戶名</…

Hadoop學習總結(MapReduce的數據去重)

現在假設有兩個數據文件 file1.txtfile2.txt2018-3-1 a 2018-3-2 b 2018-3-3 c 2018-3-4 d 2018-3-5 a 2018-3-6 b 2018-3-7 c 2018-3-3 c2018-3-1 b 2018-3-2 a 2018-3-3 b 2018-3-4 d 2018-3-5 a 2018-3-6 c 2018-3-7 d 2018-3-3 c 上述文件 file1.txt 本身包含重復數據&…

匯編-PROTO聲明過程

64位匯編 64 模式中&#xff0c;PROTO 偽指令指定程序的外部過程&#xff0c;示例如下&#xff1a; ExitProcess PROTO ;指定外部過程&#xff0c;不需要參數.code main PROCmov ebx, 0FFFFFFFFh mov ecx,0 ;結束程序call ExitProcess ;調用外部過程main ENDP END 32位…

手把手云開發小程序-(四)-uniclould增刪改查業務開發

一&#xff0c;導入uView 在開發小程序的時候&#xff0c;我習慣使用uView這個ui庫。主要是直接用當然比自己寫省時間。 它的官網&#xff1a;uView - 多平臺快速開發的UI框架 - uni-app UI框架 (gitee.io) 導入&#xff1a; npm install uview-ui2.0.31然后按照官網進行配…

UltraCompare 23 for Mac文件對比工具

UltraCompare是一款功能強大的文件比較和合并工具&#xff0c; 以下是它的特色介紹&#xff1a; 多種文件格式支持&#xff1a;UltraCompare支持比較和合并多種文件格式&#xff0c;包括文本文件、二進制文件、office文檔、PDF文件等。 文件差異高亮顯示&#xff1a;UltraComp…

內測分發平臺的合作生態和生態效應如何

大家好&#xff0c;我是咕嚕-凱撒&#xff0c;隨著移動互聯網和智能設備的快速發展&#xff0c;越來越多的開發者和企業開始關注產品的質量和體驗。而內測分發平臺則成為了一種重要的工具&#xff0c;能夠幫助他們更好地測試、優化和推廣產品。在此過程中&#xff0c;內測分發平…

特殊企業信息輕松查詢:特殊企業基本信息查詢API的實用性探討

引言 在當今數字化時代&#xff0c;企業管理和決策往往取決于有效獲取和分析關鍵信息。對于特殊企業&#xff0c;如香港公司、社會組織、律所、事業單位、基金會和新機構&#xff0c;獲取準確、及時的基本信息至關重要。在這個背景下&#xff0c;特殊企業基本信息查詢API正逐漸…

〔004〕虛幻 UE5 像素流部署

? 目錄 ? 啟用像素流插件? 打包項目? 下載環境包? 手動下載? 安裝信令服務器環境? 啟動信令服務器? 設置啟動參數? 啟動程序? 網頁運行? 開啟觸控界面? 啟用像素流插件 打開虛幻啟動程序,選擇 編輯 后點擊 插件在插件列表中搜索 pixel streaming 關鍵字,勾選后重…

springcloud宿舍管理系統源碼

開發技術&#xff1a; jdk1.8&#xff0c;mysql5.7&#xff0c;idea&#xff0c;vscode springcloud springboot mybatis vue elementui 功能介紹&#xff1a; 用戶端&#xff1a; 登錄注冊 首頁展示輪播&#xff0c;公告&#xff0c;報修&#xff0c;晚歸登記&#xff0…

提升抖音小店服務分:優化策略與實操指南

抖音小店服務分是抖音平臺為評估和提升小店服務質量而設立的一項指標。它通過對小店在訂單管理、售后服務、物流管理等多個方面的表現進行評估和計算&#xff0c;為小店提供一個可衡量的服務質量指標。提高抖音小店服務分數對于增加用戶信任度、提升銷售額和增加曝光度都非常重…

第十七章 Java鏈接數據庫

目錄 1.登錄MySQL 2.創建庫和表 3.使用Java命令查詢數據庫操作 4.右擊——點擊“Build Path”——選擇第四個——找到包的位置——導入成功 一、創建java項目 1.注冊驅動 2.獲取鏈接 3.獲取statment對象 4.執行sql語句返回結果集 5.遍歷結果集 6.關閉連接釋放資源 封裝…

opencv-python比較圖像差異性方法

OpenCV-Python提供了幾種比較兩幅圖像差異的函數&#xff0c;主要有以下幾種&#xff1a; 1.cv2.absdiff()&#xff1a;計算兩幅圖像的差異&#xff0c;并返回差異圖像。使用該函數時&#xff0c;需要先將兩幅圖像轉換為相同的大小和類型。 diff cv2.absdiff(image1, image2…