1. 程式人生 > >Windows/Linux中C++對於系統函式發生錯誤時的除錯方法(除錯Windows/Linux下建立原始socket失敗返回-1)

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使用者才能建立原始套接字。

總結

這些只是一些技巧,能解決問題的方法有很多,選擇適合自己的就行。