调用系统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
用户才能创建原始套接字。
总结
这些只是一些技巧,能解决问题的方法有很多,选择适合自己的就行。