Windows/Linux中C++對於系統函式發生錯誤時的除錯方法(除錯Windows/Linux下建立原始socket失敗返回-1)
呼叫系統API時,經常會由於操作不當導致系統函式呼叫發生錯誤,而系統API也是比較友好的,會給你一些特殊的返回值,普遍返回-1,同時,會設定一些變數,表示錯誤型別。在Windows中,呼叫GetLastError
,可以得到最近的呼叫失敗的錯誤碼;在Linux中,“全域性變數”errno
記錄了最近呼叫失敗的錯誤碼。
這裡糾正一下,errno
其實並不是全域性變數,errno
的作用是thread local
,線上程中是全域性的。
當然,這可以在程式碼中手動新增程式碼來得到這些錯誤碼,從而推斷出錯誤原因;
但是,我們不想去改程式碼,想在除錯中就推斷出系統API呼叫失敗的原因怎麼辦,有辦法的。
這裡通過今天在Windows和Linux下建立原始套接字失敗直接返回-1進行除錯。
Windows
Windows一般用Visual Studio來寫程式碼。而Visual Studio號稱宇宙最強IDE,必然有一些方便我們的地方。
我們可以在呼叫API失敗處打上一個斷點,然後在監視器中填上$err,hr
,然後向下執行這個系統呼叫,如果失敗,那麼監視器中就會顯示錯誤程式碼以及錯誤的原因描述,這個真的很方便。
int socketFd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
socketFd
為-1,運用上面的方法,得到下面的圖片。
看到這條資訊,我就知道錯誤原因了,Windows下呼叫套接字函式有那麼一套必須的程式碼流程:
#include <WinSock2.h>
#pragma comment(lib, "WS2_32")
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
WSAStartup(sockVersion, &wsaData);
加上這段程式碼就能正常呼叫socket
函數了。
Linux
Linux下,都是GNU那一套,用的是GDB除錯,同樣也是打斷點,執行系統呼叫,然後在GDB中輸入p errno
,檢視錯誤程式碼,注意,Linux下遠沒有Visual Studio那麼方便,所以,得到了錯誤碼,你要自己去檢視錯誤碼對應的錯誤原因;
這裡有個辦法,使用man errno
errno.h
標頭檔案。
第一種
man errno
會得出一些巨集觀及其對應的錯誤原因,但是這些巨集的值是多少?這個就要看標頭檔案了。
不同的Linux版本,不同的G++版本,errno.h
中的定義有點不一樣,這個要特殊情況特殊分析,舉個例子,Ubuntu 14.04 LTS中,想看到errno.h
,這個標頭檔案就要費點功夫了,得用find
或者grep
在/usr/include
中找,且,標頭檔案可能還套標頭檔案,其中,Ubuntu 14.04 LTS的錯誤碼巨集定義在/usr/include/asm-generic/errno-base.h
以及/usr/include/asm-generic/errno.h
中,這裡和MinGW的標頭檔案errno.h
對比下,看看這些巨集定義的值有哪些不一樣。
// /usr/include/asm-generic/errno-base.h
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
// MinGW/include/error.h
#define EPERM 1 /* Operation not permitted */
#define ENOFILE 2 /* No such file or directory */
#define ENOENT 2
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted function call */
#define EIO 5 /* Input/output error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Arg list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file descriptor */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Resource temporarily unavailable */
#define ENOMEM 12 /* Not enough space */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
/* 15 - Unknown Error */
#define EBUSY 16 /* strerror reports "Resource device" */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Improper link (cross-device link?) */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* Too many open files in system */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Inappropriate I/O control operation */
/* 26 - Unknown Error */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Invalid seek (seek on a pipe?) */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Domain error (math functions) */
#define ERANGE 34 /* Result too large (possibly too small) */
/* 35 - Unknown Error */
#define EDEADLOCK 36 /* Resource deadlock avoided (non-Cyg) */
#define EDEADLK 36
/* 37 - Unknown Error */
#define ENAMETOOLONG 38 /* Filename too long (91 in Cyg?) */
#define ENOLCK 39 /* No locks available (46 in Cyg?) */
#define ENOSYS 40 /* Function not implemented (88 in Cyg?) */
#define ENOTEMPTY 41 /* Directory not empty (90 in Cyg?) */
#define EILSEQ 42 /* Illegal byte sequence */
可以看出,大部分通用的錯誤程式碼的巨集定義還是一樣的,不過也有一些區別,這很正常。而且,標頭檔案中有註釋,這些就是錯誤的原因。
int socketFd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
於是在Linux下,在GDB中也運用這個方法:
發現錯誤原因是Operation not permitted
,這說明我們許可權不夠,然後查資料,得出,Linux下root
使用者才能建立原始套接字。
總結
這些只是一些技巧,能解決問題的方法有很多,選擇適合自己的就行。