程序流程
執行文件訪問攔截和 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/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系統調用的一個選項之一,它用于將數據寫入遠程進程的內存空間。這個選項通常用于修改目標進程的內存中的數據,允許一個進程追蹤和修改另一個進程的執行狀態和內存。
具體解釋如下:
-
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_INET(IPv4)或AF_INET6(IPv6)。src:指向要轉換的二進制網絡地址的指針。dst:指向用于存儲結果的緩沖區。size:dst緩沖區的大小。
inet_ntop 函數的原理是將二進制網絡地址轉換為可讀的點分十進制(IPv4)或冒號十六進制(IPv6)表示。函數根據給定的地址族 af,采用不同的格式化方式來完成這一轉換。
對于 IPv4 地址(af 為 AF_INET),inet_ntop 函數將 32 位的二進制地址拆分成四個 8 位的部分,然后將它們以點分十進制的形式表示。例如,二進制地址 0x7F000001 被轉換為字符串 “127.0.0.1”。
對于 IPv6 地址(af 為 AF_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 函數的工作原理涉及以下幾個步驟:
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,如果為NULL,realpath函數會自動為您分配內存。
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 函數或其他更安全的替代方法。