tqsheng

go.....
随笔 - 366, 文章 - 18, 评论 - 101, 引用 - 0
数据加载中……

coupon-code

http://hipromocode.com/

posted @ 2012-12-27 23:05 tqsheng 阅读(144) | 评论 (0)编辑 收藏

QWidget与HWND的互相转换

QWidget与HWND的互相转换

在编写Windows的应用程序时,我们有时不可避免地要与Windows平台固有的Win32 API打交道,但是Win32 API里面常常用到的HWND等诸多句柄QT并没有。QT作为一款优秀的跨平台GUI库,不可能未作考虑,那么需要互相转换的时候该如何做呢?

 

HWND转QWidget

1
2
3
QWidget *myWidget;
HWND hwnd;
myWidget=QWidget::find(hwnd);

QWidget转HWND

1
2
3
QWidget *myWidget;
HWND hwnd;
hwnd=(HWND)myWidget->winId();

QPixmap与HBITMAP、HICON互转

使用QPixmap::toWinHICON();QPixmap::toWinHBITMAP();
 QPixmap::fromWinHICON();QPixmap::fromWinHBITMAP();函数用法一目了然

QPixmap与QIcon、QImage可以轻松互转,这里不多说了

posted @ 2012-12-24 16:53 tqsheng 阅读(1329) | 评论 (1)编辑 收藏

经典方式 help

http://visualstudiogallery.msdn.microsoft.com/4c360395-6afd-4087-94ed-cbcbebe04a20/?SRC=Home

http://msdn.microsoft.com/en-us/subscriptions/hh442902.aspx

posted @ 2012-12-24 13:03 tqsheng 阅读(137) | 评论 (0)编辑 收藏

郭德纲道歉信全文完整内容

本人郭德纲,由于众所周知的原因,最近茅坑里扔炸弹――激起了公粪。我思前想后,觉得在博客和段子里惹恼了各界人士的估计是如下几点:我也不等那录像了,就在这里郑重宣告:周记者,我本来以为您没滚,但我错了,您滚了。
  本人郭德纲,由于众所周知的原因,最近茅坑里扔炸弹――激起了公粪。在社会各界的教育下,本人认识了自己的错误,现决定道歉如下:
  1、关于该谁道歉
  事件发生时我并不在场,打人的是我的李姓徒弟,他已经道歉了。本来我以为就没我什么事了,但后来有法律界人士教育我,说我是打人者的师傅,是公众人物,更重要的,事件发生在我家,所以我脱不了干系。
  这么一说我就明白了。虽然我那徒弟已经年满18,他犯了事连他爸都没责任,但估计在中国,师傅比爹妈责任大。要不为什么现在学生有了什么问题大家都不骂爹妈骂老师呢?所以,我该道歉
  这么一说我也明白了,为什么山西煤矿出了事得撤省长的职,因为煤老板不是公众人物,省长是啊!第一次出事,撤镇长;第二次撤县长,第三次撤市长,第四次可不就得撤到省长了嘛!就是不知道第五次、第六次该撤到哪。所以,我徒弟不是公众人物,我是,当然该我道歉。估计下次我想道还道不了了,轮到级别更高的公众人物了。咱得珍惜这次机会啊!
  这么一说我还明白了,怪不得日本人当初在南京杀了那么多人,骂他们他们还一直不服气。估计他们觉得南京是中国的地界,在中国的地界出了事怎么着中国人也有一半责任啊!当然,日本人该不该道歉不是我在这要说的,反正我是该道歉
  2、关于向谁道歉
  挨打的是那记者一个人,但激愤的是群情,对我口诛笔伐兼教育的什么人都有。看来光对那挨打的一个儿道歉还不足以平民愤,所以我郑重宣布:我对社会各界道歉
  3、关于为什么道歉
  事情的起因是我们家门口草地上那桩子,可那不是我立的。事情的另一个起因是电视台记者扛着摄像机闯到我们家里,可这事我想道歉还真道不着。事情的还有一个起因是李姓徒弟打记者了,这事仍然不是我干的。所以,我在先确定了我应该道歉的前提下,苦苦思索我该为什么事而道歉,最后得出结论,我就只好为我那博客上的文章和演出时关于此事说的段子而道歉了。我思前想后,觉得在博客和段子里惹恼了各界人士的估计是如下几点:
  4、关于“穷人”的道歉
  我说几个穷人组织了业委会,结果得罪了很多穷人。这事儿我还有点不明白,这当“穷人”到底是好事还是坏事啊?如果是好事,那我是在夸他们,不必道歉吧?如果是坏事,那我骂他们几句,应该大快人心啊!怎么就有这么多人生气呢?所以我得出的结论是,在中国,穷人这事说不得,怎么说都是错。所以我在这儿郑重纠正:我们那儿业委会那几位,不是穷人,是富人!我说错了!我是真觉得我说错了!现在在北京要买个小套二得多少钱啊?穷人住得进那别墅区吗?当然,我还得补充声明一句:我说的是一般商品房,不含广大国家干部住的福利房、经济适用房以及特价房等等,那些房子便宜是便宜,住的可多半不是穷人。我不敢误会广大国家干部是穷人,如果干部们觉得这预先声明还不够,干脆,我预先道歉
  5、关于“推搡”的道歉
  我说我那李姓徒弟和那周姓记者发生了“推搡”,引来很多群众批评我避重就轻,说打就是打,不是“推搡”。这都怪我平时学习不细致,只知道国家工作人员可以“推搡”,把人推死的有,把人房子推塌的也有,我就不知道一般老百姓就不能“推搡”。在受到大家批评后,我加强了学习,认真参考了前不久在湖北某单位门口发生了某事之后国家权威机关的权威声明,在这里,我郑重修正我的说法:李姓徒弟在与周姓记者的拉扯中行为粗暴。并请各位注意,此前我关于此事所说的所有“推搡”一律无效,改为“拉扯”。如果有哪位还认为这词不准确,我就没办法了,因为湖北那位已经住了院了,都还是“拉扯”呢,周姓记者总还没到那程度吧?
  6、关于“滚”的道歉
  根据BTV的报道,周姓记者与李姓徒弟在拉扯中从楼梯上“滚”下,我曾对此表示质疑,因为并未看到包含此类画面的录像。现在我也明白了,那录像估计还是有的,只不过有关单位出于种种原因可能不便公开而已。就像湖北某单位门口那次事件,事情都过去一个多月了,有关单位还没公布录像呢。那单位是有关单位,BTV也是有关单位,有关单位总有有关单位的道理。所以我也不等那录像了,就在这里郑重宣告:周记者,我本来以为您没滚,但我错了,您滚了。

posted @ 2012-12-20 14:54 tqsheng 阅读(280) | 评论 (0)编辑 收藏

验证码总是错误的解决办法

 上网发帖遇到了点问题,就是发帖总提示验证码错误,输入了好多次,我寻思我总不至于连数字都认不全吧,搞了半天,想了几个办法,最后终于整OK了

首先看了看是不是全角和半角的问题,还好,我没缺到用全角去输入数字验证码(切换全角和半角的是shift+空格)。

然后查杀了下木马和病毒,木马和病毒很可能会导致验证码的连续错误,呃,但是我的没有木马也没病毒。

再然后,点浏览器上面的工具--internet选项--清除cookies--确定--再在页面上刷新,估计应该没问题了,恩~~~结果还是没好使~~~

再来,还点浏览器上

posted @ 2012-12-20 14:47 tqsheng 阅读(208) | 评论 (0)编辑 收藏

linux 客户端 Socket 非阻塞connect编程(正文)


linux 客户端 Socket 非阻塞connect编程(正文)/*开发过程与源码解析
  开发测试环境:虚拟机CentOS,windows网络调试助手
  非阻塞模式有3种用途
  1.三次握手同时做其他的处理。connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网。这段时间可能有一些其他的处理要执行,比如数据准备,预处理等。
  2.用这种技术建立多个连接。这在web浏览器中很普遍.
  3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间。多数实现中,connect的超时时间在75秒到几分钟之间。有时程序希望在等待一定时间内结束,使用非阻塞connect可以防止阻塞75秒,在多线程网络编程中,尤其必要。 例如有一个通过建立线程与其他主机进行socket通信的应用程序,如果建立的线程使用阻塞connect与远程通信,当有几百个线程并发的时候,由于网络延迟而全部阻塞,阻塞的线程不会释放系统的资源,同一时刻阻塞线程超过一定数量时候,系统就不再允许建立新的线程(每个进程由于进程空间的原因能产生的线程有限),如果使用非阻塞的connect,连接失败使用select等待很短时间,如果还没有连接后,线程立刻结束释放资源,防止大量线程阻塞而使程序崩溃。
  目前connect非阻塞编程的普遍思路是:
  在一个TCP套接口设置为非阻塞后,调用connect,connect会在系统提供的errno变量中返回一个EINRPOCESS错误,此时TCP的三路握手继续进行。之后可以用select函数检查这个连接是否建立成功。以下实验基于unix网络编程和网络上给出的普遍示例,在经过大量测试之后,发现其中有很多方法,在linux中,并不适用。
  我先给出了重要源码的逐步分析,在最后给出完整的connect非阻塞源码。
  1.首先填写套接字结构,包括远程的ip,通信端口如下: */
  struct sockaddr_in serv_addr;
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_port=htons(9999);
  serv_addr.sin_addr.s_addr = inet_addr("58.31.231.255"); //inet_addr转换为网络字节序
  bzero(&(serv_addr.sin_zero),8);
  // 2.建立socket套接字:
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
  perror("socket creat error");
  return 1;
  }
  // 3.将socket建立为非阻塞,此时socket被设置为非阻塞模式
  flags = fcntl(sockfd,F_GETFL,0);//获取建立的sockfd的当前状态(非阻塞)
  fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);//将当前sockfd设置为非阻塞
  /*4. 建立connect连接,此时socket设置为非阻塞,connect调用后,无论连接是否建立立即返回-1,同时将errno(包含errno.h就可以直接使用)设置为EINPROGRESS, 表示此时tcp三次握手仍旧进行,如果errno不是EINPROGRESS,则说明连接错误,程序结束。
  当客户端和服务器端在同一台主机上的时候,connect回马上结束,并返回0;无需等待,所以使用goto函数跳过select等待函数,直接进入连接后的处理部分。*/
  if ( ( n = connect( sockfd, ( struct sockaddr *)&serv_addr , sizeof(struct sockaddr)) ) < 0 )
  {
  if(errno != EINPROGRESS) return 1;
  }
  if(n==0)
  {
  printf("connect completed immediately");
  goto done;
  }
  /* 5.设置等待时间,使用select函数等待正在后台连接的connect函数,这里需要说明的是使用select监听socket描述符是否可读或者可写,如果只可写,说明连接成功,可以进行下面的操作。如果描述符既可读又可写,分为两种情况,第一种情况是socket连接出现错误(不要问为什么,这是系统规定的,可读可写时候有可能是connect连接成功后远程主机断开了连接close(socket)),第二种情况是connect连接成功,socket读缓冲区得到了远程主机发送的数据。需要通过connect连接后返回给errno的值来进行判定,或者通过调用 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len); 函数返回值来判断是否发生错误,这里存在一个可移植性问题,在solaris中发生错误返回-1,但在其他系统中可能返回0.我首先按unix网络编程的源码进行实现。如下:*/
  FD_ZERO(&rset);
  FD_SET(sockfd,&rset);
  wset = rset;
  tval.tv_sec = 0;
  tval.tv_usec = 300000;
  int error;
  socklen_t len;
  if(( n = select(sockfd+1, &rset, &wset, NULL,&tval)) <= 0)
  {
  printf("time out connect error");
  close(sockfd);
  return -1;
  }
  If ( FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&west) )
  {
  len = sizeof(error);
  if( getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) <0)
  return 1;
  }
  /* 这里我测试了一下,按照unix网络编程的描述,当网络发生错误的时候,getsockopt返回-1,return -1,程序结束。网络正常时候返回0,程序继续执行。
  可是我在linux下,无论网络是否发生错误,getsockopt始终返回0,不返回-1,说明linux与unix网络编程还是有些细微的差别。就是说当socket描述符可读可写的时候,这段代码不起作用。不能检测出网络是否出现故障。
  我测试的方法是,当调用connect后,sleep(2)休眠2秒,借助这两秒时间将网络助手断开连接,这时候select返回2,说明套接口可读又可写,应该是网络连接的出错情况。
  此时,getsockopt返回0,不起作用。获取errno的值,指示为EINPROGRESS,没有返回unix网络编程中说的ENOTCONN,EINPROGRESS表示正在试图连接,不能表示网络已经连接失败。
针对这种情况,unix网络编程中提出了另外3种方法,这3种方法,也是网络上给出的常用的非阻塞connect示例:
  a.再调用connect一次。失败返回errno是EISCONN说明连接成功,表示刚才的connect成功,否则返回失败。 代码如下:*/
  int connect_ok;
  connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr) );
  switch (errno)
  {
  case EISCONN: //connect ok
  printf("connect OK \n");
  connect_ok = 1;
  break;
  case EALREADY:
  connect_0k = -1
  break;
  case EINPROGRESS: // is connecting, need to check again
  connect_ok = -1
  break;
  default: 
  printf("connect fail err=%d \n",errno);
  connect_ok = -1;
  break;
  }
  /*如程序所示,根据再次调用的errno返回值将connect_ok的值,来进行下面的处理,connect_ok为1继续执行其他操作,否则程序结束。
  但这种方法我在linux下测试了,当发生错误的时候,socket描述符(我的程序里是sockfd)变成可读且可写,但第二次调用connect 后,errno并没有返回EISCONN,,也没有返回连接失败的错误,仍旧是EINPROGRESS,而当网络不发生故障的时候,第二次使用 connect连接也返回EINPROGRESS,因此也无法通过再次connect来判断连接是否成功。
  b.unix网络编程中说使用read函数,如果失败,表示connect失败,返回的errno指明了失败原因,但这种方法在linux上行不通,linux在socket描述符为可读可写的时候,read返回0,并不会置errno为错误。
   c.unix网络编程中说使用getpeername函数,如果连接失败,调用该函数后,通过errno来判断第一次连接是否成功,但我试过了,无论网络连接是否成功,errno都没变化,都为EINPROGRESS,无法判断。
  悲哀啊,即使调用getpeername函数,getsockopt函数仍旧不行。
  综上方法,既然都不能确切知道非阻塞connect是否成功,所以我直接当描述符可读可写的情况下进行发送,通过能否获取服务器的返回值来判断是否成功。(如果服务器端的设计不发送数据,那就悲哀了。)
  程序的书写形式出于可移植性考虑,按照unix网络编程推荐写法,使用getsocketopt进行判断,但不通过返回值来判断,而通过函数的返回参数来判断。
  6. 用select查看接收描述符,如果可读,就读出数据,程序结束。在接收数据的时候注意要先对先前的rset重新赋值为描述符,因为select会对 rset清零,当调用select后,如果socket没有变为可读,则rset在select会被置零。所以如果在程序中使用了rset,最好在使用时候重新对rset赋值。
  程序如下:*/
  FD_ZERO(&rset);
  FD_SET(sockfd,&rset);//如果前面select使用了rset,最好重新赋值
  if( ( n = select(sockfd+1,&rset,NULL, NULL,&tval)) <= 0 )
  {
  close(sockfd);
  return -1;
  } 
  if ((recvbytes=recv(sockfd, buf, 1024, 0)) ==-1)
  {
  perror("recv error!");
  close(sockfd);
  return 1;
  }
  printf("receive num %d\n",recvbytes);
  printf("%s\n",buf);
  */
非阻塞connect

在一个TCP套接口被设置为非阻塞之后调用connect,connect会立即返回EINPROGRESS错误,表示连接操作正在进行中,但是仍未完成;同时TCP的三路握手操作继续进行;在这之后,我们可以调用select来检查这个链接是否建立成功;非阻塞connect有三种用途:
1.我们可以在三路握手的同时做一些其它的处理.connect操作要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网.在这段时间内我们可能有一些其他的处理想要执行;
2.可以用这种技术同时建立多个连接.在Web浏览器中很普遍;
3.由于我们使用select来等待连接的完成,因此我们可以给select设置一个时间限制,从而缩短connect的超时时间.在大多数实现中,connect的超时时间在75秒到几分钟之间.有时候应用程序想要一个更短的超时时间,使用非阻塞connect就是一种方法;
非阻塞connect听起来虽然简单,但是仍然有一些细节问题要处理:
1.即使套接口是非阻塞的,如果连接的服务器在同一台主机上,那么在调用connect建立连接时,连接通常会立即建立成功.我们必须处理这种情况;
2.源自Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
  A:当连接建立成功时,套接口描述符变成可写;
  B:当连接出错时,套接口描述符变成既可读又可写;
  注意:当一个套接口出错时,它会被select调用标记为既可读又可写;

非阻塞connect有这么多好处,但是处理非阻塞connect时会遇到很多可移植性问题;

处理非阻塞connect的步骤:
第一步:创建socket,返回套接口描述符;
第二步:调用fcntl把套接口描述符设置成非阻塞;
第三步:调用connect开始建立连接;
第四步:判断连接是否成功建立;
       A:如果connect返回0,表示连接简称成功(服务器可客户端在同一台机器上时就有可能发生这种情况);
       B:调用select来等待连接建立成功完成;
         如果select返回0,则表示建立连接超时;我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去;
         如果select返回大于0的值,则需要检查套接口描述符是否可读或可写;如果套接口描述符可读或可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
"读取套接口上的错误"是遇到的第一个可移植性问题;如果出现问题,getsockopt源自Berkeley的实现是返回0,等待处理的错误在变量errno中返回;但是Solaris会让getsockopt返回-1,errno置为待处理的错误;我们对这两种情况都要处理;

这样,在处理非阻塞connect时,在不同的套接口实现的平台中存在的移植性问题,首先,有可能在调用select之前,连接就已经建立成功,而且对方的数据已经到来.在这种情况下,连接成功时套接口将既可读又可写.这和连接失败时是一样的.这个时候我们还得通过getsockopt来读取错误值;这是第二个可移植性问题;
移植性问题总结:
1.对于出错的套接口描述符,getsockopt的返回值源自Berkeley的实现是返回0,待处理的错误值存储在errno中;而源自Solaris的实现是返回0,待处理的错误存储在errno中;(套接口描述符出错时调用getsockopt的返回值不可移植)
2.有可能在调用select之前,连接就已经建立成功,而且对方的数据已经到来,在这种情况下,套接口描述符是既可读又可写;这与套接口描述符出错时是一样的;(怎样判断连接是否建立成功的条件不可移植)

这样的话,在我们判断连接是否建立成功的条件不唯一时,我们可以有以下的方法来解决这个问题:
1.调用getpeername代替getsockopt.如果调用getpeername失败,getpeername返回ENOTCONN,表示连接建立失败,我们必须以SO_ERROR调用getsockopt得到套接口描述符上的待处理错误;
2.调用read,读取长度为0字节的数据.如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因.如果连接建立成功,read应该返回0;
3.再调用一次connect.它应该失败,如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的;

被中断的connect:
如果在一个阻塞式套接口上调用connect,在TCP的三路握手操作完成之前被中断了,比如说,被捕获的信号中断,将会发生什么呢?假定connect不会自动重启,它将返回EINTR.那么,这个时候,我们就不能再调用connect等待连接建立完成了,如果再次调用connect来等待连接建立完成的话,connect将会返回错误值EADDRINUSE.在这种情况下,应该做的是调用select,就像在非阻塞式connect中所做的一样.然后,select在连接建立成功(使套接口描述符可写)或连接建立失败(使套接口描述符既可读又可写)时返回;

 
 

posted @ 2012-12-18 11:44 tqsheng 阅读(5196) | 评论 (2)编辑 收藏

批处理启动服务进程后自身自动退出

 

 
  1. //AutoLockScreen.bat   
  2.   
  3. %windir%\system32\rundll32.exe user32.dll,LockWorkStation  
  4.   
  5. // AutoLockScreen.vbs   
  6.   
  7. set ws=WScript.CreateObject("WScript.Shell")  
  8.   
  9. ws.Run "c:\AutoLockScreen.bat /start",0  

直接运行AutoLockScreen.vbs即可!

如果直接执行AutoLockScreen.bat, 程序可能不会自动退出,但通过上边这种方法可以实现自动退出。

posted @ 2012-12-18 11:37 tqsheng 阅读(220) | 评论 (0)编辑 收藏

connect 是 errno 为111 115 101 22 错误分析

connect 是 errno 为111 115 101 22 错误分析

分类: 疑难杂症 网络 LINUX常用笔记 -- 网络 579人阅读 评论(0) 收藏 举报

22:参数错误,比如ip地址不合法,没有目标端口等

101:网络不可达,比如不能ping通

111:链接被拒绝,比如目标关闭链接等

115:当链接设置为非阻塞时,目标没有及时应答,返回此错误,socket可以继续使用

 

附录:Linux的错误码表(errno table)

_ 124 EMEDIUMTYPE_ Wrong medium type
_ 123 ENOMEDIUM__ No medium found
_ 122 EDQUOT___  Disk quota exceeded
_ 121 EREMOTEIO__ Remote I/O error
_ 120 EISNAM___  Is a named type file
_ 119 ENAVAIL___ No XENIX semaphores available
_ 118 ENOTNAM___ Not a XENIX named type file
_ 117 EUCLEAN___ Structure needs cleaning
_ 116 ESTALE___  Stale NFS file handle
_ 115 EINPROGRESS  +Operation now in progress
_ 114 EALREADY__  Operation already in progress
_ 113 EHOSTUNREACH  No route to host
_ 112 EHOSTDOWN__ Host is down
_ 111 ECONNREFUSED  Connection refused
_ 110 ETIMEDOUT_  +Connection timed out
_ 109 ETOOMANYREFS  Too many references: cannot splice
_ 108 ESHUTDOWN__ Cannot send after transport endpoint shutdown
_ 107 ENOTCONN__  Transport endpoint is not connected
_ 106 EISCONN___ Transport endpoint is already connected
_ 105 ENOBUFS___ No buffer space available
_ 104 ECONNRESET_  Connection reset by peer
_ 103 ECONNABORTED  Software caused connection abort
_ 102 ENETRESET__ Network dropped connection on reset
_ 101 ENETUNREACH_ Network is unreachable
_ 100 ENETDOWN__  Network is down
_  99 EADDRNOTAVAIL Cannot assign requested address
_  98 EADDRINUSE_  Address already in use
_  97 EAFNOSUPPORT  Address family not supported by protocol
_  96 EPFNOSUPPORT  Protocol family not supported
_  95 EOPNOTSUPP_  Operation not supported
_  94 ESOCKTNOSUPPORT Socket type not supported
_  93 EPROTONOSUPPORT Protocol not supported
_  92 ENOPROTOOPT_ Protocol not available
_  91 EPROTOTYPE_  Protocol wrong type for socket
_  90 EMSGSIZE__ +Message too long
_  89 EDESTADDRREQ  Destination address required
_  88 ENOTSOCK__  Socket operation on non-socket
_  87 EUSERS___  Too many users
_  86 ESTRPIPE__  Streams pipe error
_  85 ERESTART__  Interrupted system call should be restarted
_  84 EILSEQ___  Invalid or incomplete multibyte or wide character
_  83 ELIBEXEC__  Cannot exec a shared library directly
_  82 ELIBMAX___ Attempting to link in too many shared libraries
_  81 ELIBSCN___ .lib section in a.out corrupted
_  80 ELIBBAD___ Accessing a corrupted shared library
_  79 ELIBACC___ Can not access a needed shared library
_  78 EREMCHG___ Remote address changed
_  77 EBADFD___  File descriptor in bad state
_  76 ENOTUNIQ__  Name not unique on network
_  75 EOVERFLOW__ Value too large for defined data type
_  74 EBADMSG__  +Bad message
_  73 EDOTDOT___ RFS specific error
_  72 EMULTIHOP__ Multihop attempted
_  71 EPROTO___  Protocol error
_  70 ECOMM____ Communication error on send
_  69 ESRMNT___  Srmount error
_  68 EADV____  Advertise error
_  67 ENOLINK___ Link has been severed
_  66 EREMOTE___ Object is remote
_  65 ENOPKG___  Package not installed
_  64 ENONET___  Machine is not on the network
_  63 ENOSR____ Out of streams resources
_  62 ETIME____ Timer expired
_  61 ENODATA___ No data available
_  60 ENOSTR___  Device not a stream
_  59 EBFONT___  Bad font file format
_  57 EBADSLT___ Invalid slot
_  56 EBADRQC___ Invalid request code
_  55 ENOANO___  No anode
_  54 EXFULL___  Exchange full
_  53 EBADR____ Invalid request descriptor
_  52 EBADE____ Invalid exchange
_  51 EL2HLT___  Level 2 halted
_  50 ENOCSI___  No CSI structure available
_  49 EUNATCH___ Protocol driver not attached
_  48 ELNRNG___  Link number out of range
_  47 EL3RST___  Level 3 reset
_  46 EL3HLT___  Level 3 halted
_  45 EL2NSYNC__  Level 2 not synchronized
_  44 ECHRNG___  Channel number out of range
_  43 EIDRM____ Identifier removed
_  42 ENOMSG___  No message of desired type
_  40 ELOOP____ Too many levels of symbolic links
_  39 ENOTEMPTY_  +Directory not empty
_  38 ENOSYS___ +Function not implemented
_  37 ENOLCK___ +No locks available
_  36 ENAMETOOLONG +File name too long
_  35 EDEADLK__  +Resource deadlock avoided
_  34 ERANGE___ +Numerical result out of range
_  33 EDOM____ +Numerical argument out of domain
_  32 EPIPE___  +Broken pipe
_  31 EMLINK___ +Too many links
_  30 EROFS___  +Read-only file system
_  29 ESPIPE___ +Illegal seek
_  28 ENOSPC___ +No space left on device
_  27 EFBIG___  +File too large
_  26 ETXTBSY___ Text file busy
_  25 ENOTTY___ +Inappropriate ioctl for device
_  24 EMFILE___ +Too many open files
_  23 ENFILE___ +Too many open files in system
_  22 EINVAL___ +Invalid argument
_  21 EISDIR___ +Is a directory
_  20 ENOTDIR__  +Not a directory
_  19 ENODEV___ +No such device
_  18 EXDEV___  +Invalid cross-device link
_  17 EEXIST___ +File exists
_  16 EBUSY___  +Device or resource busy
_  15 ENOTBLK___ Block device required
_  14 EFAULT___ +Bad address
_  13 EACCES___ +Permission denied
_  12 ENOMEM___ +Cannot allocate memory
_  11 EAGAIN___ +Resource temporarily unavailable
_  10 ECHILD___ +No child processes
__ 9 EBADF___  +Bad file descriptor
__ 8 ENOEXEC__  +Exec format error
__ 7 E2BIG___  +Argument list too long
__ 6 ENXIO___  +No such device or address
__ 5 EIO____  +Input/output error
__ 4 EINTR___  +Interrupted system call
__ 3 ESRCH___  +No such process
__ 2 ENOENT___ +No such file or directory
__ 1 EPERM___  +Operation not permitted
#_  0 --_____  Success

posted @ 2012-12-18 11:36 tqsheng 阅读(5267) | 评论 (0)编辑 收藏

GNC金卡周活动

GNC金卡周活动,每月都有,月月有惊喜


使用规则:

1、 金卡周是每月1-7日,所以从上个月的最后一周开始接受金卡周GNC商品的预定,采购时间是下月的1-7日。

2、 商品的价格,无其他优惠的商品都是8折,有其他优惠,并且其他优惠>8折的,仍按其他优惠计算。(只能享受一种优惠)

3、 所有参加活动的客户,不管买多买少,都享受免美国运费的优惠。

4、 国际运费按商品实际重量计算,超过4磅的建议直邮,低于4磅的建议中转,客户可以自己选择确定。

非睿莫思商城,美国代购,http://www.freemerce.com

posted @ 2012-12-15 22:30 tqsheng 阅读(144) | 评论 (0)编辑 收藏

vlc

http://jeremiah.blog.51cto.com/539865/d-1/p-2

posted @ 2012-12-10 17:16 tqsheng 阅读(172) | 评论 (0)编辑 收藏

雾化器

天卓睿丰医疗器械(北京)有限公司
地址:北京市东城区东直门外大街48号东方银座写字楼25L室
邮编:100027
电话:01084477765 
传真:01084477289

免费咨询电话:800 810 1906(工作日9:00至18:00开通,仅座机支持)

posted @ 2012-12-08 21:55 tqsheng 阅读(201) | 评论 (0)编辑 收藏

Program Library HOWTO

     摘要: Program Library HOWTOProgram Library HOWTODavid A. Wheelerversion 1.20, 11 April 2003This HOWTO for programmers discusses how to create and use program libraries on Linux. This includes static librari...  阅读全文

posted @ 2012-12-07 23:24 tqsheng 阅读(348) | 评论 (0)编辑 收藏

ELF文件格式及程序加载执行过程总汇

     摘要: 好文转自: http://www.linuxsir.org/bbs/printthread.php?t=206356这是我这段时间学习elf文件格式搜集的资料,其中的一些重量级文档,比如linkers and loaders ,the executable and linkable format等等就不贴出来了,太大----文章列表为:elf文件格式-- 1elf文件格式-- 2elf文件格式--...  阅读全文

posted @ 2012-12-07 23:17 tqsheng 阅读(5455) | 评论 (0)编辑 收藏

HTTP协议详解

引言                                        

HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
HTTP协议的主要特点可概括如下:
1.支持客户/服务器模式。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

 

 

一、HTTP协议详解之URL篇

    http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。

HTTP URL (URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息)的格式如下:
http://host[":"port][abs_path]
http表示要通过HTTP协议来定位网络资源;host表示合法的Internet主机域名或者IP地址;port指定一个端口号,为空则使用缺省端口80;abs_path指定请求资源的URI;如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常这个工作浏览器自动帮我们完成。
eg:
1、输入:www.guet.edu.cn
浏览器自动转换成:http://www.guet.edu.cn/
2、http:192.168.0.116:8080/index.jsp 

 

 

 

二、HTTP协议详解之请求篇

    http请求由三部分组成,分别是:请求行、消息报头、请求正文

1、请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:Method Request-URI HTTP-Version CRLF  
其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。

请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET     请求获取Request-URI所标识的资源
POST    在Request-URI所标识的资源后附加新的数据
HEAD    请求获取由Request-URI所标识的资源的响应消息报头
PUT     请求服务器存储一个资源,并用Request-URI作为其标识
DELETE  请求服务器删除Request-URI所标识的资源
TRACE   请求服务器回送收到的请求信息,主要用于测试或诊断
CONNECT 保留将来使用
OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
应用举例:
GET方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用GET方法向服务器获取资源,eg:GET /form.html HTTP/1.1 (CRLF)

POST方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。
eg:POST /reg.jsp HTTP/ (CRLF)
Accept:image/gif,image/x-xbit,... (CRLF)
...
HOST:www.guet.edu.cn (CRLF)
Content-Length:22 (CRLF)
Connection:Keep-Alive (CRLF)
Cache-Control:no-cache (CRLF)
(CRLF)         //该CRLF表示消息报头已经结束,在此之前为消息报头
user=jeffrey&pwd=1234  //此行以下为提交的数据

HEAD方法与GET方法几乎是一样的,对于HEAD请求的回应部分来说,它的HTTP头部中包含的信息与通过GET请求所得到的信息是相同的。利用这个方法,不必传输整个资源内容,就可以得到Request-URI所标识的资源的信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。
2、请求报头后述
3、请求正文(略) 

 

三、HTTP协议详解之响应篇

    在接收和解释请求消息后,服务器返回一个HTTP响应消息。

HTTP响应也是由三个部分组成,分别是:状态行、消息报头、响应正文
1、状态行格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码、状态描述、说明:
200 OK      //客户端请求成功
400 Bad Request  //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报                 //头域一起使用 
403 Forbidden  //服务器收到请求,但是拒绝提供服务
404 Not Found  //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error //服务器发生不可预期的错误
503 Server Unavailable  //服务器当前不能处理客户端的请求,一段时间后,                         //可能恢复正常
eg:HTTP/1.1 200 OK (CRLF)

2、响应报头后述

3、响应正文就是服务器返回的资源的内容 

 

四、HTTP协议详解之消息报头篇

    HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始行(对于请求消息,开始行就是请求行,对于响应消息,开始行就是状态行),消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成。

HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。
每一个报头域都是由名字+“:”+空格+值 组成,消息报头域的名字是大小写无关的。

1、普通报头
在普通报头中,有少数报头域用于所有的请求和响应消息,但并不用于被传输的实体,只用于传输的消息。
eg:
Cache-Control   用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制),HTTP1.0使用的类似的报头域为Pragma。
请求时的缓存指令包括:no-cache(用于指示请求或响应消息不能缓存)、no-store、max-age、max-stale、min-fresh、only-if-cached;
响应时的缓存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.
eg:为了指示IE浏览器(客户端)不要缓存页面,服务器端的JSP程序可以编写如下:response.sehHeader("Cache-Control","no-cache");
//response.setHeader("Pragma","no-cache");作用相当于上述代码,通常两者//合用
这句代码将在发送的响应消息中设置普通报头域:Cache-Control:no-cache


Date普通报头域表示消息产生的日期和时间

Connection普通报头域允许发送指定连接的选项。例如指定连接是连续,或者指定“close”选项,通知服务器,在响应完成后,关闭连接

2、请求报头
请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。
常用的请求报头
Accept
Accept请求报头域用于指定客户端接受哪些类型的信息。eg:Accept:image/gif,表明客户端希望接受GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本。
Accept-Charset
Accept-Charset请求报头域用于指定客户端接受的字符集。eg:Accept-Charset:iso-8859-1,gb2312.如果在请求消息中没有设置这个域,缺省是任何字符集都可以接受。
Accept-Encoding
Accept-Encoding请求报头域类似于Accept,但是它是用于指定可接受的内容编码。eg:Accept-Encoding:gzip.deflate.如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。
Accept-Language
Accept-Language请求报头域类似于Accept,但是它是用于指定一种自然语言。eg:Accept-Language:zh-cn.如果请求消息中没有设置这个报头域,服务器假定客户端对各种语言都可以接受。
Authorization
Authorization请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时,如果收到服务器的响应代码为401(未授权),可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行验证。
Host(发送请求时,该报头域是必需的)
Host请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的,eg:
我们在浏览器中输入:http://www.guet.edu.cn/index.html
浏览器发送的请求消息中,就会包含Host请求报头域,如下:
Host:www.guet.edu.cn
此处使用缺省端口号80,若指定了端口号,则变成:Host:www.guet.edu.cn:指定端口号
User-Agent
我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息。User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过,这个报头域不是必需的,如果我们自己编写一个浏览器,不使用User-Agent请求报头域,那么服务器端就无法得知我们的信息了。
请求报头举例:
GET /form.html HTTP/1.1 (CRLF)
Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/* (CRLF)
Accept-Language:zh-cn (CRLF)
Accept-Encoding:gzip,deflate (CRLF)
If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)
If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)
User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)
Host:www.guet.edu.cn (CRLF)
Connection:Keep-Alive (CRLF)
(CRLF)

3、响应报头
响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。
常用的响应报头
Location
Location响应报头域用于重定向接受者到一个新的位置。Location响应报头域常用在更换域名的时候。
Server
Server响应报头域包含了服务器用来处理请求的软件信息。与User-Agent请求报头域是相对应的。下面是
Server响应报头域的一个例子:
Server:Apache-Coyote/1.1
WWW-Authenticate
WWW-Authenticate响应报头域必须被包含在401(未授权的)响应消息中,客户端收到401响应消息时候,并发送Authorization报头域请求服务器对其进行验证时,服务端响应报头就包含该报头域。
eg:WWW-Authenticate:Basic realm="Basic Auth Test!"  //可以看出服务器对请求资源采用的是基本验证机制。


4、实体报头
请求和响应消息都可以传送一个实体。一个实体由实体报头域和实体正文组成,但并不是说实体报头域和实体正文要在一起发送,可以只发送实体报头域。实体报头定义了关于实体正文(eg:有无实体正文)和请求所标识的资源的元信息。
常用的实体报头
Content-Encoding
Content-Encoding实体报头域被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。Content-Encoding这样用于记录文档的压缩方法,eg:Content-Encoding:gzip
Content-Language
Content-Language实体报头域描述了资源所用的自然语言。没有设置该域则认为实体内容将提供给所有的语言阅读
者。eg:Content-Language:da
Content-Length
Content-Length实体报头域用于指明实体正文的长度,以字节方式存储的十进制数字来表示。
Content-Type
Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型。eg:
Content-Type:text/html;charset=ISO-8859-1
Content-Type:text/html;charset=GB2312
Last-Modified
Last-Modified实体报头域用于指示资源的最后修改日期和时间。
Expires
Expires实体报头域给出响应过期的日期和时间。为了让代理服务器或浏览器在一段时间以后更新缓存中(再次访问曾访问过的页面时,直接从缓存中加载,缩短响应时间和降低服务器负载)的页面,我们可以使用Expires实体报头域指定页面过期的时间。eg:Expires:Thu,15 Sep 2006 16:23:12 GMT
HTTP1.1的客户端和缓存必须将其他非法的日期格式(包括0)看作已经过期。eg:为了让浏览器不要缓存页面,我们也可以利用Expires实体报头域,设置为0,jsp中程序如下:response.setDateHeader("Expires","0");

 

 

五、利用telnet观察http协议的通讯过程

    实验目的及原理:
    利用MS的telnet工具,通过手动输入http请求信息的方式,向服务器发出请求,服务器接收、解释和接受请求后,会返回一个响应,该响应会在telnet窗口上显示出来,从而从感性上加深对http协议的通讯过程的认识。

    实验步骤:

1、打开telnet
1.1 打开telnet
运行-->cmd-->telnet

1.2 打开telnet回显功能
set localecho

2、连接服务器并发送请求
2.1 open www.guet.edu.cn 80  //注意端口号不能省略

    HEAD /index.asp HTTP/1.0
    Host:www.guet.edu.cn
    
   /*我们可以变换请求方法,请求桂林电子主页内容,输入消息如下*/
    open www.guet.edu.cn 80 
   
    GET /index.asp HTTP/1.0  //请求资源的内容
    Host:www.guet.edu.cn  

2.2 open www.sina.com.cn 80  //在命令提示符号下直接输入telnet www.sina.com.cn 80
    HEAD /index.asp HTTP/1.0
    Host:www.sina.com.cn
 

3 实验结果:

3.1 请求信息2.1得到的响应是:

HTTP/1.1 200 OK                                              //请求成功
Server: Microsoft-IIS/5.0                                    //web服务器
Date: Thu,08 Mar 200707:17:51 GMT
Connection: Keep-Alive                                 
Content-Length: 23330
Content-Type: text/html
Expries: Thu,08 Mar 2007 07:16:51 GMT
Set-Cookie: ASPSESSIONIDQAQBQQQB=BEJCDGKADEDJKLKKAJEOIMMH; path=/
Cache-control: private

//资源内容省略

3.2 请求信息2.2得到的响应是:

HTTP/1.0 404 Not Found       //请求失败
Date: Thu, 08 Mar 2007 07:50:50 GMT
Server: Apache/2.0.54 <Unix>
Last-Modified: Thu, 30 Nov 2006 11:35:41 GMT
ETag: "6277a-415-e7c76980"
Accept-Ranges: bytes
X-Powered-By: mod_xlayout_jh/0.0.1vhs.markII.remix
Vary: Accept-Encoding
Content-Type: text/html
X-Cache: MISS from zjm152-78.sina.com.cn
Via: 1.0 zjm152-78.sina.com.cn:80<squid/2.6.STABLES-20061207>
X-Cache: MISS from th-143.sina.com.cn
Connection: close


失去了跟主机的连接

按任意键继续...


4 .注意事项:1、出现输入错误,则请求不会成功。
          2、报头域不分大小写。
          3、更深一步了解HTTP协议,可以查看RFC2616,在http://www.letf.org/rfc上找到该文件。
          4、开发后台程序必须掌握http协议

 

六、HTTP协议相关技术补充

    1、基础:
    高层协议有:文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等
中介由三种:代理(Proxy)、网关(Gateway)和通道(Tunnel),一个代理根据URI的绝对格式来接受请求,重写全部或部分消息,通过 URI的标识把已格式化过的请求发送到服务器。网关是一个接收代理,作为一些其它服务器的上层,并且如果必须的话,可以把请求翻译给下层的服务器协议。一 个通道作为不改变消息的两个连接之间的中继点。当通讯需要通过一个中介(例如:防火墙等)或者是中介不能识别消息的内容时,通道经常被使用。
     代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的 服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处 理没有被用户代理完成的请求。
网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
  网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
    通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继 的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。


2、协议分析的优势—HTTP分析器检测网络攻击
以模块化的方式对高层协议进行分析处理,将是未来入侵检测的方向。
HTTP及其代理的常用端口80、3128和8080在network部分用port标签进行了规定

3、HTTP协议Content Lenth限制漏洞导致拒绝服务攻击
使用POST方法时,可以设置ContentLenth来定义需要传送的数据长度,例如ContentLenth:999999999,在传送完成前,内 存不会释放,攻击者可以利用这个缺陷,连续向WEB服务器发送垃圾数据直至WEB服务器内存耗尽。这种攻击方法基本不会留下痕迹。
http://www.cnpaf.net/Class/HTTP/0532918532667330.html


4、利用HTTP协议的特性进行拒绝服务攻击的一些构思
服务器端忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,这种情况我们称作:服务器端受到了SYNFlood攻击(SYN洪水攻击)。
而Smurf、TearDrop等是利用ICMP报文来Flood和IP碎片攻击的。本文用“正常连接”的方法来产生拒绝服务攻击。
19端口在早期已经有人用来做Chargen攻击了,即Chargen_Denial_of_Service,但是!他们用的方法是在两台Chargen 服务器之间产生UDP连接,让服务器处理过多信息而DOWN掉,那么,干掉一台WEB服务器的条件就必须有2个:1.有Chargen服务2.有HTTP 服务
方法:攻击者伪造源IP给N台Chargen发送连接请求(Connect),Chargen接收到连接后就会返回每秒72字节的字符流(实际上根据网络实际情况,这个速度更快)给服务器。


5、Http指纹识别技术
   Http指纹识别的原理大致上也是相同的:记录不同服务器对Http协议执行中的微小差别进行识别.Http指纹识别比TCP/IP堆栈指纹识别复杂许 多,理由是定制Http服务器的配置文件、增加插件或组件使得更改Http的响应信息变的很容易,这样使得识别变的困难;然而定制TCP/IP堆栈的行为 需要对核心层进行修改,所以就容易识别.
      要让服务器返回不同的Banner信息的设置是很简单的,象Apache这样的开放源代码的Http服务器,用户可以在源代码里修改Banner信息,然 后重起Http服务就生效了;对于没有公开源代码的Http服务器比如微软的IIS或者是Netscape,可以在存放Banner信息的Dll文件中修 改,相关的文章有讨论的,这里不再赘述,当然这样的修改的效果还是不错的.另外一种模糊Banner信息的方法是使用插件。
常用测试请求:
1:HEAD/Http/1.0发送基本的Http请求
2:DELETE/Http/1.0发送那些不被允许的请求,比如Delete请求
3:GET/Http/3.0发送一个非法版本的Http协议请求
4:GET/JUNK/1.0发送一个不正确规格的Http协议请求
Http指纹识别工具Httprint,它通过运用统计学原理,组合模糊的逻辑学技术,能很有效的确定Http服务器的类型.它可以被用来收集和分析不同Http服务器产生的签名。


6、其他:为了提高用户使用浏览器时的性能,现代浏览器还支持并发的访问方式,浏览一个网页时同时建立多个连接,以迅速获得一个网页上的多个图标,这样能更快速完成整个网页的传输。
HTTP1.1中提供了这种持续连接的方式,而下一代HTTP协议:HTTP-NG更增加了有关会话控制、丰富的内容协商等方式的支持,来提供
更高效率的连接。

posted @ 2012-12-07 23:03 tqsheng 阅读(363) | 评论 (0)编辑 收藏

可执行文件(ELF)格式的理解

ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF是构成众多xNIX系统的基础之一,所以作为嵌入式Linux系统乃至内核驱动程序开发人员,你最好熟悉并掌握它。

其实,关于ELF这个主题,网络上已经有相当多的文章存在,但是其介绍的内容比较分散,使得初学者不太容易从中得到一个系统性的认识。为了帮助大家学习,我这里打算写一系列连贯的文章来介绍ELF以及相关的应用。这是这个系列中的第一篇文章,主要是通过不同工具的使用来熟悉ELF文件的内部结构以及相关的基本概念。后面的文章,我们会介绍很多高级的概念和应用,比方动态链接和加载,动态库的开发,C语言Main函数是被谁以及如何被调用的,ELF格式在内核中的支持,Linux内核中对ELF section的扩展使用等等。

好的,开始我们的第一篇文章。在详细进入正题之前,先给大家介绍一点ELF文件格式的参考资料。在ELF格式出来之后,TISC(Tool Interface Standard Committee)委员会定义了一套ELF标准。你可以从这里(http://refspecs.freestandards.org/elf/)找到详细的标准文档。TISC委员会前后出了两个版本,v1.1和v1.2。两个版本内容上差不多,但就可读性上来讲,我还是推荐你读 v1.2的。因为在v1.2版本中,TISC重新组织原本在v1.1版本中的内容,将它们分成为三个部分(books):

a) Book I

介绍了通用的适用于所有32位架构处理器的ELF相关内容

b) Book II

介绍了处理器特定的ELF相关内容,这里是以Intel x86 架构处理器作为例子介绍

c) Book III

介绍了操作系统特定的ELF相关内容,这里是以运行在x86上面的 UNIX System V.4 作为例子介绍

值得一说的是,虽然TISC是以x86为例子介绍ELF规范的,但是如果你是想知道非x86下面的ELF实现情况,那也可以在http://refspecs.freestandards.org/elf/中找到特定处理器相关的Supplment文档。比方ARM相关的,或者MIPS相关的等等。另外,相比较UNIX系统的另外一个分支BSD Unix,Linux系统更靠近 System V 系统。所以关于操作系统特定的ELF内容,你可以直接参考v1.2标准中的内容。

这里多说些废话:别忘了 Linus 在实现Linux的第一个版本的时候,就是看了介绍Unix内部细节的书:《The of the Unix Operating System》,得到很多启发。这本书对应的操作系统是System V 的第二个Release。这本书介绍了操作系统的很多设计观念,并且行文简单易懂。所以虽然现在的Linux也吸取了其他很多Unix变种的设计理念,但是如果你想研究学习Linux内核,那还是以看这本书作为开始为好。这本书也是我在接触Linux内核之前所看的第一本介绍操作系统的书,所以我极力向大家推荐。(在学校虽然学过操作系统原理,但学的也是很糟糕最后导致期末考试才四十来分,记忆仿佛还在昨天:))

好了,还是回来开始我们第一篇ELF主题相关的文章吧。这篇文章主要是通过使用不同的工具来分析对象文件,来使你掌握ELF文件的基本格式,以及了解相关的基本概念。你在读这篇文章的时候,希望你在电脑上已经打开了那个 v1.2 版本的ELF规范,并对照着文章内容看规范里的文字。

首先,你需要知道的是所谓对象文件(Object files)有三个种类:

1) 可重定位的对象文件(Relocatable file)

这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。如何产生 Relocatable file,你应该很熟悉了,请参见我们相关的基本概念文章和JulWiki。另外,可以预先告诉大家的是我们的内核可加载模块 .ko 文件也是 Relocatable object file。

2) 可执行的对象文件(Executable file)

这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。你应该已经知道,在我们的 Linux 系统里面,存在两种可执行的东西。除了这里说的 Executable object file,另外一种就是可执行的脚本(如shell脚本)。注意这些脚本不是 Executable object file,它们只是文本文件,但是执行这些脚本所用的解释器就是 Executable object file,比如 bash shell 程序。

3) 可被共享的对象文件(Shared object file)

这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程中,必须经过两个步骤:

a) 链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。

b) 在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。

以上所提到的 link editor 以及 dynamic linker 是什么东西,你可以参考我们基本概念中的相关文章。对于什么是编译器,汇编器等你应该也已经知道,在这里只是使用他们而不再对他们进行详细介绍。为了下面的叙述方便,你可以下载test.tar.gz包,解压缩后使用"make"进行编译。编译完成后,会在目录中生成一系列的ELF对象文件,更多描述见里面的 README 文件。我们下面的论述都基于这些产生的对象文件。

make所产生的文件,包括 sub.o/sum.o/test.o/libsub.so/test 等等都是ELF对象文件。至于要知道它们都属于上面三类中的哪一种,我们可以使用 file 命令来查看:

[yihect@juliantec test]$ file sum.o sub.o test.o libsub.so test 
sum.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped 
sub.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped 
test.o:    ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped 
libsub.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped 
test:      ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped

结果很清楚的告诉我们他们都属于哪一个类别。比方 sum.o 是应用在x86架构上的可重定位文件。这个结果也间接的告诉我们,x86是小端模式(LSB)的32位结构。那对于 file 命令来说,它又能如何知道这些信息?答案是在ELF对象文件的最前面有一个ELF文件头,里面记载了所适用的处理器、对象文件类型等各种信息。在TISCv1.2的规范中,用下面的图描述了ELF对象文件的基本组成,其中ELF文件头赫然在目。

ELF 文件头

等等,为什么会有左右两个很类似的图来说明ELF的组成格式?这是因为ELF格式需要使用在两种场合:

a) 组成不同的可重定位文件,以参与可执行文件或者可被共享的对象文件的链接构建;

b) 组成可执行文件或者可被共享的对象文件,以在运行时内存中进程映像的构建。

所以,基本上,图中左边的部分表示的是可重定位对象文件的格式;而右边部分表示的则是可执行文件以及可被共享的对象文件的格式。正如TISCv1.2规范中所阐述的那样,ELF文件头被固定地放在不同类对象文件的最前面。至于它里面的内容,除了file命令所显示出来的那些之外,更重要的是包含另外一些数据,用于描述ELF文件中ELF文件头之外的内容。如果你的系统中安装有 GNU binutils 包,那我们可以使用其中的 readelf 工具来读出整个ELF文件头的内容,比如:

[yihect@juliantec test]$ readelf -h ./sum.o ELF Header:   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00    Class:                             ELF32   Data:                              2's complement, little endian   Version:                           1 (current)   OS/ABI:                            UNIX - System V   ABI Version:                       0   Type:                              REL (Relocatable file)   Machine:                           Intel 80386   Version:                           0x1   Entry point address:               0x0   Start of program headers:          0 (bytes into file)   Start of section headers:          184 (bytes into file)   Flags:                             0x0   Size of this header:               52 (bytes)   Size of program headers:           0 (bytes)   Number of program headers:         0   Size of section headers:           40 (bytes)   Number of section headers:         9   Section header string table index: 6  

这个输出结果能反映出很多东西。那如何来看这个结果中的内容,我们还是就着TISCv1.2规范来。在实际写代码支持ELF格式对象文件格式的时候,我们都会定义许多C语言的结构来表示ELF格式的各个相关内容,比方这里的ELF文件头,你就可以在TISCv1.2规范中找到这样的结构定义(注意我们研究的是针对x86架构的ELF,所以我们只考虑32位版本,而不考虑其他如64位之类的):

ELF 文件头结构

这个结构里面出现了多种数据类型,同样可以在规范中找到相关说明:

ELF 相关数据类型

在我们以后一系列文章中,我们会着重拿实际的程序代码来分析,介时你会在头文件中找到同样的定义。但是这里,我们只讨论规范中的定义,暂不考虑任何程序代码。在ELF头中,字段e_machine和e_type指明了这是针对x86架构的可重定位文件,最前面有个长度为16字节的字段中有一个字节表示了它适用于32bits机器,而不是64位的。除了这些之外,另外ELF头还告诉了我们其他一些特别重要的信息,分别是:

a) 这个sum.o的进入点是0x0(e_entry),这表面Relocatable objects不会有程序进入点。所谓程序进入点是指当程序真正执行起来的时候,其第一条要运行的指令的运行时地址。因为Relocatable objects file只是供再链接而已,所以它不存在进入点。而可执行文件test和动态库.so都存在所谓的进入点,你可以用 readelf -h 看看。后面我们的文章中会介绍可执行文件的e_entry指向C库中的_start,而动态库.so中的进入点指向 call_gmon_start。这些后面再说,这里先不深入讨论。

b) 这个sum.o文件包含有9个sections,但却没有segments(Number of program headers为0)。

那什么是所谓 sections 呢?可以说,sections 是在ELF文件里头,用以装载内容数据的最小容器。在ELF文件里面,每一个 sections 内都装载了性质属性都一样的内容,比方:

1) .text section 里装载了可执行代码;

2) .data section 里面装载了被初始化的数据;

3) .bss section 里面装载了未被初始化的数据;

4) 以 .rec 打头的 sections 里面装载了重定位条目;

5) .symtab 或者 .dynsym section 里面装载了符号信息;

6) .strtab 或者 .dynstr section 里面装载了字符串信息;

7) 其他还有为满足不同目的所设置的section,比方满足调试的目的、满足动态链接与加载的目的等等。

一个ELF文件中到底有哪些具体的 sections,由包含在这个ELF文件中的 section head table(SHT)决定。在SHT中,针对每一个section,都设置有一个条目,用来描述对应的这个section,其内容主要包括该 section 的名称、类型、大小以及在整个ELF文件中的字节偏移位置等等。我们也可以在TISCv1.2规范中找到SHT表中条目的C结构定义:

ELF section header entry

我们可以像下面那样来使用 readelf 工具来查看可重定位对象文件 sum.o 的SHT表内容:[yihect@juliantec test]$ readelf -S ./sum.o 
There are 9 section headers, starting at offset 0xb8: 
  
Section Headers: 
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al 
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0 
  [ 1] .text             PROGBITS        00000000 000034 00000b 00  AX  0   0  4 
  [ 2] .data             PROGBITS        00000000 000040 000004 00  WA  0   0  4 
  [ 3] .bss              NOBITS          00000000 000044 000000 00  WA  0   0  4 
  [ 4] .note.GNU-stack   PROGBITS        00000000 000044 000000 00      0   0  1 
  [ 5] .comment          PROGBITS        00000000 000044 00002d 00      0   0  1 
  [ 6] .shstrtab         STRTAB          00000000 000071 000045 00      0   0  1 
  [ 7] .symtab           SYMTAB          00000000 000220 0000a0 10      8   7  4 
  [ 8] .strtab           STRTAB          00000000 0002c0 00001d 00      0   0  1 
Key to Flags: 
  W (write), A (alloc), X (execute), M (merge), S (strings) 
  I (info), L (link order), G (group), x (unknown) 
  O (extra OS processing required) o (OS specific), p (processor specific)

这个结果显示了 sum.o 中包含的所有9个sections。因为sum.o仅仅是参与link editor链接的可重定位文件,而不参与最后进程映像的构建,所以Addr(sh_addr)为0。后面你会看到可执行文件以及动态库文件中大部分sections的这一字段都是有某些取值的。Off(sh_offset)表示了该section离开文件头部位置的距离。Size(sh_size)表示section的字节大小。ES(sh_entsize)只对某些形式的sections 有意义。比方符号表 .symtab section,其内部包含了一个表格,表格的每一个条目都是特定长度的,那这里的这个字段就表示条目的长度10。Al(sh_addralign)是地址对齐要求。另外剩下的两列Lk和Inf,对应着条目结构中的字段sh_link和字段sh_info。它们中记录的是section head table 中的条目索引,这就意味着,从这两个字段出发,可以找到对应的另外两个 section,其具体的含义解释依据不同种类的 section 而不同,后面会介绍。

注意上面结果中的 Flg ,表示的是对应section的相关标志。比方.text section 里面存储的是代码,所以就是只读的(X);.data和.bss里面存放的都是可写的(W)数据(非在堆栈中定义的数据),只不过前者存的是初始化过的数据,比方程序中定义的赋过初值的全局变量等;而后者里面存储的是未经过初始化的数据。因为未经过初始化就意味着不确定这些数据刚开始的时候会有些什么样的值,所以针对对象文件来说,它就没必要为了存储这些数据而在文件内多留出一块空间,因此.bss section的大小总是为0。后面会看到,当可执行程序被执行的时候,动态连接器会在内存中开辟一定大小的空间来存放这些未初始化的数据,里面的内存单元都被初始化成0。可执行程序文件中虽然没有长度非0的 .bss section,但却记录有在程序运行时,需要开辟多大的空间来容纳这些未初始化的数据。

另外一个标志A说明对应的 section 是Allocable的。所谓 Allocable 的section,是指在运行时,进程(process)需要使用它们,所以它们被加载器加载到内存中去。

而与此相反,存在一些non-Allocable 的sections,它们只是被链接器、调试器或者其他类似工具所使用的,而并非参与进程的运行中去的那些 section。比方后面要介绍的字符串表section .strtab,符号表 .symtab section等等。当运行最后的可执行程序时,加载器会加载那些 Allocable 的部分,而 non-Allocable 的部分则会被继续留在可执行文件内。所以,实际上,这些 non-Allocable 的section 都可以被我们用 stip 工具从最后的可执行文件中删除掉,删除掉这些sections的可执行文件照样能够运行,只不过你没办法来进行调试之类的事情罢了。

我们仍然可以使用 readelf -x SecNum 来倾印出不同 section 中的内容。但是,无奈其输出结果都是机器码,对我们人来说不具备可读性。所以我们换用 binutils 包中的另外一个工具 objdump 来看看这些 sections 中到底具有哪些内容,先来看看 .text section 的:[yihect@juliantec test]$ objdump -d -j .text ./sum.o 
  
./sum.o:     file format elf32-i386 
  
Disassembly of section .text: 
  
00000000 : 
   0:   55                      push   %ebp 
   1:   89 e5                   mov    %esp,%ebp 
   3:   8b 45 0c                mov    0xc(%ebp),%eax 
   6:   03 45 08                add    0x8(%ebp),%eax 
   9:   c9                      leave  
   a:   c3                      ret

objdump 的选项 -d 表示要对由 -j 选择项指定的 section 内容进行反汇编,也就是由机器码出发,推导出相应的汇编指令。上面结果显示在 sum.o 对象文件的 .text 中只是包含了函数 sum_func 的定义。用同样的方法,我们来看看 sum.o 中 .data section 有什么内容:[yihect@juliantec test]$ objdump -d -j .data  ./sum.o 
  
./sum.o:     file format elf32-i386 
  
Disassembly of section .data: 
  
00000000 : 
   0:   17 00 00 00                                         ....

这个结果显示在 sum.o 的 .data section 中定义了一个四字节的变量 gv_inited,其值被初始化成 0x00000017,也就是十进制值 23。别忘了,x86架构是使用小端模式的。

我们接下来来看看字符串表section .strtab。你可以选择使用 readelf -x :

[yihect@juliantec test]$ readelf -x 8 ./sum.o 
  
Hex dump of section '.strtab': 
  0x00000000 64657469 6e695f76 6700632e 6d757300 .sum.c.gv_inited 
  0x00000010       00 68630063 6e75665f 6d757300 .sum_func.ch.

上面命令中的 8 是 .strtab section 在SHT表格中的索引值,从上面所查看的SHT内容中可以找到。尽管这个命令的输出结果不是那么具有可读性,但我们还是得来说一说如何看这个结果,因为后续文章中将会使用大量的这种命令。上面结果中的十六进制数据部分从右到左看是地址递增的方向,而字符内容部分从左到右看是地址递增的方向。所以,在 .strtab section 中,按照地址递增的方向来看,各字节的内容依次是 0x00、0x73、0x75、0x6d、0x2e ....,也就是字符 、's'、'u'、'm'、'.' ... 等。如果还是看不太明白,你可以使用 hexdump 直接dumping出 .strtab section 开头(其偏移在文件内0x2c0字节处)的 32 字节数据:

[yihect@juliantec test]$ hexdump -s 0x2c0 -n 32 -c ./sum.o 
00002c0     s   u   m   .   c     g   v   _   i   n   i   t   e   d 
00002d0     s   u   m   _   f   u   n   c     c   h              
00002dd

.strtab section 中存储着的都是以字符 为分割符的字符串,这些字符串所表示的内容,通常是程序中定义的函数名称、所定义过的变量名称等等。。。当对象文件中其他地方需要和一个这样的字符串相关联的时候,往往会在对应的地方存储 .strtab section 中的索引值。比方下面将要介绍的符号表 .symtab section 中,有一个条目是用来描述符号 gv_inited 的,那么在该条目中就会有一个字段(st_name)记录着字符串 gv_inited 在 .strtab section 中的索引 7 。 .shstrtab 也是字符串表,只不过其中存储的是 section 的名字,而非所函数或者变量的名称。

字符串表在真正链接和生成进程映像过程中是不需要使用的,但是其对我们调试程序来说就特别有帮助,因为我们人看起来最舒服的还是自然形式的字符串,而非像天书一样的数字符号。前面使用objdump来反汇编 .text section 的时候,之所以能看到定义了函数 sum_func ,那也是因为存在这个字符串表的原因。当然起关键作用的,还是符号表 .symtab section 在其中作为中介,下面我们就来看看符号表。

虽然我们同样可以使用 readelf -x 来查看符号表(.symtab)section的内容,但是其结果可读性太差,我们换用 readelf -s 或者 objdump -t 来查看(前者输出结果更容易看懂):

[yihect@juliantec test]$ readelf -s ./sum.o 
  
Symbol table '.symtab' contains 10 entries: 
   Num:    Value  Size Type    Bind   Vis      Ndx Name 
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS sum.c 
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    2 
     4: 00000000     0 SECTION LOCAL  DEFAULT    3 
     5: 00000000     0 SECTION LOCAL  DEFAULT    4 
     6: 00000000     0 SECTION LOCAL  DEFAULT    5 
     7: 00000000     4 OBJECT  GLOBAL DEFAULT    2 gv_inited 
     8: 00000000    11 FUNC    GLOBAL DEFAULT    1 sum_func 
     9: 00000001     1 OBJECT  GLOBAL DEFAULT  COM ch

在符号表内针对每一个符号,都会相应的设置一个条目。在继续介绍上面的结果之前,我们还是从规范中找出符号表内条目的C结构定义:

ELF 符号表条目

上面结果中 Type 列显示出符号的种类。Bind 列定义了符号的绑定类型。种类和绑定类型合并在一起,由结构中 st_info 字段来定义。在ELF格式中,符号类型总共可以有这么几种:

ELF 符号类型

类型 STT_OBJECT 表示和该符号对应的是一个数据对象,比方程序中定义过的变量、数组等,比方上面的 gv_inited 和 ch;类型 STT_FUNC 表示该符号对应的是函数,比方上面的 sum_func函数。类型 STT_SECTION 表示该符号和一个 section 相关,这种符号用于重定位。关于重定位,我们下文会介绍。

符号的绑定类型表示了这个符号的可见性,是仅本对象文件可见呢,还是全局可见。它的取值主要有三种:STB_LOCA、STB_GLOBAL和STB_WEAK,具体的内容还请参见规范。关于符号,最重要的就是符号的值(st_value)了。依据对象文件的不同类型,符号的值所表示的含义也略有差异:

a) 在可重定位文件中,如果该符号对应的section index(上面的Ndx)为SHN_COMMON,那么符号的值表示的是该数据的对齐要求,比方上面的变量 ch 。

b) 在可重定位文件中,除去上面那条a中定义的符号,对于其他的符号来说,其值表示的是对应 section 内的偏移值。比方 gv_inited 变量定义在 .data section 的最前面,所以其值为0。

c) 在可执行文件或者动态库中,符号的值表示的是运行时的内存地址。

好,咱们再来介绍重定位。在所产生的对象文件 test.o 中有对函数 sum_func 的引用,这对我们的x386结构来说,其实就是一条call指令。既然 sum_func 是定义在 sum.o 中的,那对 test.o 来说,它就是一个外部引用。所以,汇编器在产生 test.o 的时候,它会产生一个重定位条目。重定位条目中会包含以下几类东西:

1) 它会包含一个符号表中一个条目的索引,因为这样我们才知道它具体是哪个符号需要被重定位的;

2) 它会包含一个 .text section 中的地址单元的偏移值。原本这个偏移值处的地址单元里面应该存放着 call 指令的操作数。对上面来说,也就是函数 sum_func 的地址,但是目前这个地址汇编器还不知道。

3) 它还会包含一个tag,以指明该重定位属于何种类型。

当我们用链接器去链接这个对象文件的时候,链接器会遍历所有的重定位条目,碰到像 sum_func 这样的外部引用,它会找到 sum_func 的确切地址,并且把它写回到上面 call 指令操作数所占用的那个地址单元。像这样的操作,称之为重定位操作。link editor 和 dynamic linker 都要完成一些重定位操作,只不过后者的动作更加复杂,因为它是在运行时动态完成的,我们以后的文章会介绍相关的内容。概括一下,所谓重定位操作就是:“汇编的时候产生一个空坐位,上面用红纸写着要坐在这个座位上的人的名字,然后连接器在开会前安排那个人坐上去”。

如前面我们说过的,对象文件中的重定位条目,会构成一个个单独的 section。这些 section 的名字,常会是这样的形式:".rel.XXX"。其中XXX表示的是这些重定位条目所作用到的section,如 .text section。重定位条目所构成的section需要和另外两个section产生关联:符号表section(表示要重定位的是哪一个符号)以及受影响地址单元所在的section。在使用工具来查看重定位section之前,我们先从规范中找出来表示重定位条目的结构定义(有两种,依处理器架构来定):

ELF 重定位条目结构定义

结构中 r_offset 对于可重定位文件.o来说,就是地址单元的偏移值(前面的b条);另外对可执行文件或者动态库来说,就是该地址单元的运行时地址。上面 a条中的符号表内索引和c条中的类型,一起构成了结构中的字段 r_info。

重定位过程在计算最终要放到受影响地址单元中的时候,需要加上一个附加的数 addend。当某一种处理器选用 Elf32_Rela 结构的时候,该 addend 就是结构中的 r_addend 字段;否则该 addend 就是原本存储在受影响地址单元中的原有值。x86架构选用 Elf32_Rel 结构来表示重定位条目。ARM架构也是用这个。

重定位类型意味着如何去修改受影响的地址单元,也就是按照何种方式去计算需要最后放在受影响单元里面的值。具体的重定位类型有哪些,取决与特定的处理器架构,你可以参考相关规范。这种计算方式可以非常的简单,比如在x386上的 R_386_32 类型,它规定只是将附加数加上符号的值作为所需要的值;该计算方式也可以是非常的复杂,比如老版本ARM平台上的 R_ARM_PC26。在这篇文章的末尾,我会详细介绍一种重定位类型:R_386_PC32。至于另外一些重要的重定位类型,如R_386_GOTPC,R_386_PLT32,R_386_GOT32,R_386_GLOB_DAT 以及 R_386_JUMP_SLOT 等。读者可以先自己研究,也许我们会在后面后面的文章中讨论到相关主题时再行介绍。

我们可以使用命令 readelf -r 来查看重定位信息:

[yihect@juliantec test_2]$ readelf -r test.o 
  
Relocation section '.rel.text' at offset 0x464 contains 8 entries: 
Offset     Info    Type            Sym.Value  Sym. Name 
00000042  00000902 R_386_PC32        00000000   sub_func 
00000054  00000a02 R_386_PC32        00000000   sum_func 
0000005d  00000a02 R_386_PC32        00000000   sum_func 
0000007a  00000501 R_386_32          00000000   .rodata 
0000007f  00000b02 R_386_PC32        00000000   printf 
0000008d  00000c02 R_386_PC32        00000000   double_gv_inited 
00000096  00000501 R_386_32          00000000   .rodata 
0000009b  00000b02 R_386_PC32        00000000   printf

至此,ELF对象文件格式中的 linking view ,也就是上面组成图的左边部分,我们已经介绍完毕。在这里最重要的概念是 section。在可重定位文件里面,section承载了大多数被包含的东西,代码、数据、符号信息、重定位信息等等。可重定位对象文件里面的这些sections是作为输入,给链接器那去做链接用的,所以这些 sections 也经常被称做输入 section。

链接器在链接可执行文件或动态库的过程中,它会把来自不同可重定位对象文件中的相同名称的 section 合并起来构成同名的 section。接着,它又会把带有相同属性(比方都是只读并可加载的)的 section 都合并成所谓 segments(段)。segments 作为链接器的输出,常被称为输出section。我们开发者可以控制哪些不同.o文件的sections来最后合并构成不同名称的 segments。如何控制呢,就是通过 linker script 来指定。关于链接器脚本,我们这里不予讨论。

一个单独的 segment 通常会包含几个不同的 sections,比方一个可被加载的、只读的segment 通常就会包括可执行代码section .text、只读的数据section .rodata以及给动态链接器使用的符号section .dymsym等等。section 是被链接器使用的,但是 segments 是被加载器所使用的。加载器会将所需要的 segment 加载到内存空间中运行。和用 sections header table 来指定一个可重定位文件中到底有哪些 sections 一样。在一个可执行文件或者动态库中,也需要有一种信息结构来指出包含有哪些 segments。这种信息结构就是 program header table,如ELF对象文件格式中右边的 execute view 所示的那样。

我们可以用 readelf -l 来查看可执行文件的程序头表,如下所示:

[yihect@juliantec test_2]$ readelf -l ./test 
  
Elf file type is EXEC (Executable file) 
Entry point 0x8048464 
There are 7 program headers, starting at offset 52 
  
Program Headers: 
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align 
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4 
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1 
      [Requesting program interpreter: /lib/ld-linux.so.2] 
  LOAD           0x000000 0x08048000 0x08048000 0x0073c 0x0073c R E 0x1000 
  LOAD           0x00073c 0x0804973c 0x0804973c 0x00110 0x00118 RW  0x1000 
  DYNAMIC        0x000750 0x08049750 0x08049750 0x000d0 0x000d0 RW  0x4 
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4 
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4 
  
Section to Segment mapping: 
  Segment Sections... 
   00     
   01     .interp 
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag 
   06

结果显示,在可执行文件 ./test 中,总共有7个 segments。同时,该结果也很明白显示出了哪些 section 映射到哪一个 segment 当中去。比方在索引为2的那个segment 中,总共有15个 sections 映射进来,其中包括我们前面提到过的 .text section。注意这个segment 有两个标志: R 和 E。这个表示该segment是可读的,也可执行的。如果你看到标志中有W,那表示该segment是可写的。

我们还是来解释一下上面的结果,希望你能对照着TISCv1.2规范里面的文本来看,我这里也列出程序头表条目的C结构:

ELF 程序头表项

上面类型为PHDR的segment,用来包含程序头表本身。类型为INTERP的segment只包含一个 section,那就是 .interp。在这个section中,包含了动态链接过程中所使用的解释器路径和名称。在Linux里面,这个解释器实际上就是 /lib/ ,这可以通过下面的 hexdump 看出来:[yihect@juliantec test_2]$ hexdump -s 0x114 -n 32 -C  ./test  
00000114  2f 6c 69 62 2f 6c 64 2d  6c 69 6e 75 78 2e 73 6f  |/lib/ld-linux.so| 
00000124  2e 32 00 00 04 00 00 00  10 00 00 00 01 00 00 00  |.2..............| 
00000134

为什么会有这样的一个 segment?这是因为我们写的应用程序通常都需要使用动态链接库.so,就像 test 程序中所使用的 libsub.so 一样。我们还是先大致说说程序在linux里面是怎么样运行起来的吧。当你在 shell 中敲入一个命令要执行时,内核会帮我们创建一个新的进程,它在往这个新进程的进程空间里面加载进可执行程序的代码段和数据段后,也会加载进动态连接器(在Linux里面通常就是 /lib/ld-linux.so 符号链接所指向的那个程序,它本省就是一个动态库)的代码段和数据。在这之后,内核将控制传递给动态链接库里面的代码。动态连接器接下来负责加载该命令应用程序所需要使用的各种动态库。加载完毕,动态连接器才将控制传递给应用程序的main函数。如此,你的应用程序才得以运行。

这里说的只是大致的应用程序启动运行过程,更详细的,我们会在后续的文章中继续讨论。我们说link editor链接的应用程序只是部分链接过的应用程序。经常的,在应用程序中,会使用很多定义在动态库中的函数。最最基础的比方C函数库(其本身就是一个动态库)中定义的函数,每个应用程序总要使用到,就像我们test程序中使用到的 printf 函数。为了使得应用程序能够正确使用动态库,动态连接器在加载动态库后,它还会做更进一步的链接,这就是所谓的动态链接。为了让动态连接器能成功的完成动态链接过程,在前面运行的link editor需要在应用程序可执行文件中生成数个特殊的 sections,比方 .dynamic、.dynsym、.got和.plt等等。这些内容我们会在后面的文章中进行讨论。

我们先回到上面所输出的文件头表中。在接下来的数个 segments 中,最重要的是三个 segment:代码段,数据段和堆栈段。代码段和堆栈段的 VirtAddr 列的值分别为 0x08048000 和 0x0804973c。这是什么意思呢?这是说对应的段要加载在进程虚拟地址空间中的起始地址。虽然在可执行文件中规定了 text segment和 data segment 的起始地址,但是最终,在内存中的这些段的真正起始地址,却可能不是这样的,因为在动态链接器加载这些段的时候,需要考虑到页面对齐的因素。为什么?因为像x86这样的架构,它给内存单元分配读写权限的最小单位是页(page)而不是字节。也就是说,它能规定从某个页开始、连续多少页是只读的。却不能规定从某个页内的哪一个字节开始,连续多少个字节是只读的。因为x86架构中,一个page大小是4k,所以,动态链接器在加载 segment 到虚拟内存中的时候,其真实的起始地址的低12位都是零,也即以 0x1000 对齐。

我们先来看看一个真实的进程中的内存空间信息,拿我们的 test 程序作为例子。在 Linux 系统中,有一个特殊的由内核实现的虚拟文件系统 /proc。内核实现这个文件系统,并将它作为整个Linux系统面向外部世界的一个接口。我们可以通过 /proc 观察到一个正在运行着的Linux系统的内核数据信息以及各进程相关的信息。所以我们如果要查看某一个进程的内存空间情况,也可以通过它来进行。使用/proc唯一需要注意的是,由于我们的 test 程序很小,所以当我们运行起来之后,它很快就会结束掉,使得我们没有时间去查看test的进程信息。我们需要想办法让它继续运行,或者最起码运行直到让我们能从 /proc 中获取得到想要的信息后再结束。

我们有多种选择。最简单的是,在 test main 程序中插入一个循环,然后在循环中放入 sleep() 的调用,这样当程序运行到这个循环的时候,就会进入“运行-睡眠-运行-睡眠”循环中。这样我们就有机会去看它的虚拟内存空间信息。另外一个方法,是使用调试器,如GDB。我们设置一个断点,然后在调试过程中让test进程在这个断点处暂停,这样我们也有机会获得地址空间的信息。我们这里就使用这种方法。当然,为了能让GDB调试我们的 test,我们得在编译的时候加上"-g"选项。最后我们用下面的命令得到 test 程序对应进程的地址空间信息。

[yihect@juliantec ~]$ cat /proc/`pgrep test`/maps 
00103000-00118000 r-xp 00000000 08:02 544337     /lib/ld-2.3.4.so 
00118000-00119000 r--p 00015000 08:02 544337     /lib/ld-2.3.4.so 
00119000-0011a000 rw-p 00016000 08:02 544337     /lib/ld-2.3.4.so 
0011c000-00240000 r-xp 00000000 08:02 544338     /lib/tls/libc-2.3.4.so 
00240000-00241000 r--p 00124000 08:02 544338     /lib/tls/libc-2.3.4.so 
00241000-00244000 rw-p 00125000 08:02 544338     /lib/tls/libc-2.3.4.so 
00244000-00246000 rw-p 00244000 00:00 0 
00b50000-00b51000 r-xp 00000000 08:02 341824     /usr/lib/libsub.so 
00b51000-00b52000 rw-p 00000000 08:02 341824     /usr/lib/libsub.so 
08048000-08049000 r-xp 00000000 08:05 225162     /home/yihect/test_2/test 
08049000-0804a000 rw-p 00000000 08:05 225162     /home/yihect/test_2/test 
b7feb000-b7fed000 rw-p b7feb000 00:00 0 
b7fff000-b8000000 rw-p b7fff000 00:00 0 
bff4c000-c0000000 rw-p bff4c000 00:00 0 
ffffe000-fffff000 ---p 00000000 00:00 0

注意,上面命令中的pgre test 是用`括起来的,它不是单引号,而是键盘上 Esc 字符下面的那个字符。从这个结果上可以看出,所有的段,其起始地址和结束地址(前面两列)都是0x1000对齐的。结果中也列出了对应的段是从哪里引过来的,比方动态链接器/lib/ld-2.3.4.so、C函数库和test程序本身。注意看test程序引入的代码段起始地址是 0x08048000,这和我们 ELF 文件中指定的相同,但是结束地址却是0x08049000,和文件中指定的不一致(0x08048000+0x0073c=0x0804873c)。这里,其实加载器也把数据segment中开头一部分也映射进了 text segment 中去;同样的,进程虚拟内存空间中的 data segment 从 08049000 开始,而可执行文件中指定的是从 0x0804973c 开始。所以加载器也把代码segment中末尾一部分也映射进了 data segment 中去了。

从程序头表中我们可以看到一个类型为 GNU_STACK 的segment,这是 stack segment。程序头表中的这一项,除了 Flg/Align 两列不为空外, 其他列都为0。这是因为堆栈段在虚拟内存空间中,从哪里开始、占多少字节是由内核说了算的,而不决定于可执行程序。实际上,内核决定把堆栈段放在整个进程地址空间的用户空间的最上面,所以堆栈段的末尾地址就是 0xc0000000。别忘记在 x86 中,堆栈是从高向低生长的。

好,为了方便你对后续文章的理解,我们在这里讨论一种比较简单的重定位类型 R_386_PC32。前面我们说过重定义的含义,也即在连接阶段,根据某种计算方式计算出一个新的值(通常是地址),然后将这个值重新改写到对象文件或者内存映像中某个section中的某个地址单元中去的这样一个过程。那所谓重定位类型,就规定了使用何种方式,去计算这个值。既然是计算,那就肯定需要涉及到所要纳入计算的变量。实际上,具体有哪些变量参与计算如同如何进行计算一样也是不固定的,各种重定位类型有自己的规定。

根据规范里面的规定,重定位类型 R_386_PC32 的计算需要有三个变量参与:S,A和P。其计算方式是 S+A-P。根据规范,当R_386_PC32类型的重定位发生在 link editor 链接若干个 .o 对象文件从而形成可执行文件的过程中的时候,变量S指代的是被重定位的符号的实际运行时地址,而变量P是重定位所影响到的地址单元的实际运行时地址。在运行于x86架构上的Linux系统中,这两个地址都是虚拟地址。变量A最简单,就是重定位所需要的附加数,它是一个常数。别忘了x86架构所使用的重定位条目结构体类型是 Elf32_Rela,所以附加数就存在于受重定位影响的地址单元中。重定位最后将计算得到的值patch到这个地址单元中。

或许,咱们举一个实际例子来阐述可能对你更有用。在我们的 test 程序中,test.c 的 main 函数中需要调用定义在 sum.o 中的 sum_func 函数,所以link editor 在将 test.o/sum.o 联结成可执行文件 test 的时候,必须处理一个重定位,这个重定位就是 R_386_PC32 类型的。我们先用 objdump 来查看 test.o 中的 .text section 内容(我只选取了前面一部分):[yihect@juliantec test_2]$ objdump -d -j .text ./test.o 
  
./test.o:     file format elf32-i386 
  
Disassembly of section .text: 
  
00000000 <main />: 
   0:   55                      push   %ebp 
   1:   89 e5                   mov    %esp,%ebp 
   3:   83 ec 18                sub    $0x18,%esp 
   6:   83 e4 f0                and    $0xfffffff0,%esp 
   9:   b8 00 00 00 00          mov    $0x0,%eax 
   e:   83 c0 0f                add    $0xf,%eax 
  11:   83 c0 0f                add    $0xf,%eax 
  14:   c1 e8 04                shr    $0x4,%eax 
  17:   c1 e0 04                shl    $0x4,%eax 
  1a:   29 c4                   sub    %eax,%esp 
  1c:   c7 45 fc 0a 00 00 00    movl   $0xa,0xfffffffc(%ebp) 
  23:   c7 45 f8 2d 00 00 00    movl   $0x2d,0xfffffff8(%ebp) 
  2a:   c7 45 f4 03 00 00 00    movl   $0x3,0xfffffff4(%ebp) 
  31:   c7 45 f0 48 00 00 00    movl   $0x48,0xfffffff0(%ebp) 
  38:   83 ec 08                sub    $0x8,%esp 
  3b:   ff 75 f0                pushl  0xfffffff0(%ebp) 
  3e:   ff 75 f4                pushl  0xfffffff4(%ebp) 
  41:   e8 fc ff ff ff          call   42 
  46:   83 c4 08                add    $0x8,%esp 
  49:   50                      push   %eax 
  4a:   83 ec 0c                sub    $0xc,%esp 
  4d:   ff 75 f8                pushl  0xfffffff8(%ebp) 
  50:   ff 75 fc                pushl  0xfffffffc(%ebp) 
  53:   e8 fc ff ff ff          call   54 
  58:   83 c4 14                add    $0x14,%esp 
  ......

如结果所示,在离开 .text section 开始 0x53 字节的地方,有一条call指令。这条指令是对 sum_func 函数的调用,objdump 将其反汇编成 call 54,这是因为偏移 0x54 字节的地方原本应该放着 sum_func 函数的地址,但现在因为 sum_func 定义在 sum.o 中,所以这个地方就是重定位需要做 patch 的地址单元所在处。我们注意到,这个地址单元的值为 0xfffffffc,也就是十进制的 -4(计算机中数是用补码表示的)。所以,参与重定位运算的变量A就确定了,即是 -4。

我们在 test.o 中找出影响该地址单元的重定位记录如下:

[yihect@juliantec test_2]$ readelf -r ./test.o |  grep 54 
00000054  00000a02 R_386_PC32        00000000   sum_func

果然,如你所见,该条重定位记录是 R_386_PC32 类型的。前面变量A确定了,那么另外两个变量S和变量P呢?从正向去计算这两个变量的值比较麻烦。尽管我们知道,在Linux里面,链接可执行程序时所使用的默认的链接器脚本将最后可执行程序的 .text segment 起始地址设置在 0x08048000的位置。但是,从这个地址出发,去寻找符号(函数)sub_func 和 上面受重定位影响的地址单元的运行时地址的话,需要经过很多人工计算,所以比较麻烦。

相反的,我们使用objdump工具像下面这样分析最终链接生成的可执行程序 ./test 的 .text segment 段,看看函数 sum_func 和 那个受影响单元的运行时地址到底是多少,这是反向的查看链接器的链接结果。链接器在链接的过程中是正向的将正确的地址分配给它们的。

[yihect@juliantec test_2]$ objdump -d -j .text ./test 
  
./test:     file format elf32-i386 
  
Disassembly of section .text: 
  
08048498 : 
8048498:       31 ed                   xor    %ebp,%ebp 
...... 
08048540 <main />: 
...... 
804858a:       83 ec 0c                sub    $0xc,%esp 
804858d:       ff 75 f8                pushl  0xfffffff8(%ebp) 
8048590:       ff 75 fc                pushl  0xfffffffc(%ebp) 
8048593:       e8 74 00 00 00          call   804860c 
8048598:       83 c4 14                add    $0x14,%esp 
804859b:       50                      push   %eax 
...... 

0804860c : 
804860c:       55                      push   %ebp 
804860d:       89 e5                   mov    %esp,%ebp 
804860f:       8b 45 0c                mov    0xc(%ebp),%eax 
8048612:       03 45 08                add    0x8(%ebp),% 
8048615:       c9                      leave  
8048616:       c3                      ret    
8048617:       90                      nop 
  
......

从中很容易的就可以看出,链接器给函数 sum_func 分配的运行时地址是 0x0804860c,所以变量S的值就是 0x0804860c。那么变量P呢?它表示的是重定位所影响地址单元的运行地址。如果要计算这个地址,我们可以先看看 main 函数的运行时地址,再加上0x54字节的偏移来得到。从上面看出 main 函数的运行时地址为 0x08048540,所以重定位所影响地址单元的运行时地址为 0x08048540+0x54 = 0x08048594。所以重定位计算的最终结果为:

S+A-P = 0x0804860c+(-4)-0x08048594 = 0x00000074

从上面可以看出,链接器在链接过程中,确实也把这个计算得到的结果存储到了上面 call 指令操作数所在的地址单元中去了。那么,程序在运行时,是如何凭借这样一条带有如此操作数的 call 指令来调用到(或者跳转到)函数 sum_func 中去的呢?

你看,调用者 main 和被调用者 sum_func 处在同一个text segment中。根据x86架构或者IBM兼容机的汇编习惯,段内转移或者段内跳转时使用的寻址方式是PC相对寻址。也就是若要让程序从一个段内的A处,跳转到同一段内的B处,那么PC相对寻址会取程序在A处执行时的PC值,再加上某一个偏移值(offset),得到要跳转的目标地址(B处地址)。那么,对于x86架构来说,由于有规定,PC总是指向下一条要执行的指令,那么当程序执行在call指令的时候,PC指向的是下一条add指令,其值也就是 0x8048598。最后,寻址的时候再加上call指令的操作数0x74作为偏移,计算最终的 sum_func 函数目标地址为 0x8048598+0x74 = 0x804860c。

有点意思吧:),如果能绕出来,那说明我们是真的明白了,其实,绕的过程本身就充满着趣味性,就看你自己的心态了。说到这里,本文行将结束。本文所介绍的很多内容,可能在某些同学眼中会过于简单,但是为了体现知识的完整性、同时也为了让大家先有个基础以便更容易的看后续的文章,我们还是在这里介绍一下ELF格式的基础知识。下面一篇关于ELF主题的文章,将详细介绍动态连接的内在实现。届时,你将看到大量的实际代码挖掘。

posted @ 2012-12-07 22:59 tqsheng 阅读(261) | 评论 (0)编辑 收藏

仅列出标题
共25页: 1 2 3 4 5 6 7 8 9 Last