• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            tqsheng

            go.....
            隨筆 - 366, 文章 - 18, 評論 - 101, 引用 - 0
            數據加載中……

            coupon-code

            http://hipromocode.com/

            posted @ 2012-12-27 23:05 tqsheng 閱讀(146) | 評論 (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 閱讀(1333) | 評論 (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 閱讀(139) | 評論 (0)編輯 收藏

            郭德綱道歉信全文完整內容

            本人郭德綱,由于眾所周知的原因,最近茅坑里扔炸彈――激起了公糞。我思前想后,覺得在博客和段子里惹惱了各界人士的估計是如下幾點:我也不等那錄像了,就在這里鄭重宣告:周記者,我本來以為您沒滾,但我錯了,您滾了。
              本人郭德綱,由于眾所周知的原因,最近茅坑里扔炸彈――激起了公糞。在社會各界的教育下,本人認識了自己的錯誤,現決定道歉如下:
              1、關于該誰道歉
              事件發生時我并不在場,打人的是我的李姓徒弟,他已經道歉了。本來我以為就沒我什么事了,但后來有法律界人士教育我,說我是打人者的師傅,是公眾人物,更重要的,事件發生在我家,所以我脫不了干系。
              這么一說我就明白了。雖然我那徒弟已經年滿18,他犯了事連他爸都沒責任,但估計在中國,師傅比爹媽責任大。要不為什么現在學生有了什么問題大家都不罵爹媽罵老師呢?所以,我該道歉
              這么一說我也明白了,為什么山西煤礦出了事得撤省長的職,因為煤老板不是公眾人物,省長是啊!第一次出事,撤鎮長;第二次撤縣長,第三次撤市長,第四次可不就得撤到省長了嘛!就是不知道第五次、第六次該撤到哪。所以,我徒弟不是公眾人物,我是,當然該我道歉。估計下次我想道還道不了了,輪到級別更高的公眾人物了。咱得珍惜這次機會啊!
              這么一說我還明白了,怪不得日本人當初在南京殺了那么多人,罵他們他們還一直不服氣。估計他們覺得南京是中國的地界,在中國的地界出了事怎么著中國人也有一半責任?。‘斎?,日本人該不該道歉不是我在這要說的,反正我是該道歉。
              2、關于向誰道歉
              挨打的是那記者一個人,但激憤的是群情,對我口誅筆伐兼教育的什么人都有??磥砉鈱δ前ご虻囊粋€兒道歉還不足以平民憤,所以我鄭重宣布:我對社會各界道歉。
              3、關于為什么道歉
              事情的起因是我們家門口草地上那樁子,可那不是我立的。事情的另一個起因是電視臺記者扛著攝像機闖到我們家里,可這事我想道歉還真道不著。事情的還有一個起因是李姓徒弟打記者了,這事仍然不是我干的。所以,我在先確定了我應該道歉的前提下,苦苦思索我該為什么事而道歉,最后得出結論,我就只好為我那博客上的文章和演出時關于此事說的段子而道歉了。我思前想后,覺得在博客和段子里惹惱了各界人士的估計是如下幾點:
              4、關于“窮人”的道歉
              我說幾個窮人組織了業委會,結果得罪了很多窮人。這事兒我還有點不明白,這當“窮人”到底是好事還是壞事???如果是好事,那我是在夸他們,不必道歉吧?如果是壞事,那我罵他們幾句,應該大快人心啊!怎么就有這么多人生氣呢?所以我得出的結論是,在中國,窮人這事說不得,怎么說都是錯。所以我在這兒鄭重糾正:我們那兒業委會那幾位,不是窮人,是富人!我說錯了!我是真覺得我說錯了!現在在北京要買個小套二得多少錢???窮人住得進那別墅區嗎?當然,我還得補充聲明一句:我說的是一般商品房,不含廣大國家干部住的福利房、經濟適用房以及特價房等等,那些房子便宜是便宜,住的可多半不是窮人。我不敢誤會廣大國家干部是窮人,如果干部們覺得這預先聲明還不夠,干脆,我預先道歉!
              5、關于“推搡”的道歉
              我說我那李姓徒弟和那周姓記者發生了“推搡”,引來很多群眾批評我避重就輕,說打就是打,不是“推搡”。這都怪我平時學習不細致,只知道國家工作人員可以“推搡”,把人推死的有,把人房子推塌的也有,我就不知道一般老百姓就不能“推搡”。在受到大家批評后,我加強了學習,認真參考了前不久在湖北某單位門口發生了某事之后國家權威機關的權威聲明,在這里,我鄭重修正我的說法:李姓徒弟在與周姓記者的拉扯中行為粗暴。并請各位注意,此前我關于此事所說的所有“推搡”一律無效,改為“拉扯”。如果有哪位還認為這詞不準確,我就沒辦法了,因為湖北那位已經住了院了,都還是“拉扯”呢,周姓記者總還沒到那程度吧?
              6、關于“滾”的道歉
              根據BTV的報道,周姓記者與李姓徒弟在拉扯中從樓梯上“滾”下,我曾對此表示質疑,因為并未看到包含此類畫面的錄像?,F在我也明白了,那錄像估計還是有的,只不過有關單位出于種種原因可能不便公開而已。就像湖北某單位門口那次事件,事情都過去一個多月了,有關單位還沒公布錄像呢。那單位是有關單位,BTV也是有關單位,有關單位總有有關單位的道理。所以我也不等那錄像了,就在這里鄭重宣告:周記者,我本來以為您沒滾,但我錯了,您滾了。

            posted @ 2012-12-20 14:54 tqsheng 閱讀(287) | 評論 (0)編輯 收藏

            驗證碼總是錯誤的解決辦法

             上網發帖遇到了點問題,就是發帖總提示驗證碼錯誤,輸入了好多次,我尋思我總不至于連數字都認不全吧,搞了半天,想了幾個辦法,最后終于整OK了

            首先看了看是不是全角和半角的問題,還好,我沒缺到用全角去輸入數字驗證碼(切換全角和半角的是shift+空格)。

            然后查殺了下木馬和病毒,木馬和病毒很可能會導致驗證碼的連續錯誤,呃,但是我的沒有木馬也沒病毒。

            再然后,點瀏覽器上面的工具--internet選項--清除cookies--確定--再在頁面上刷新,估計應該沒問題了,恩~~~結果還是沒好使~~~

            再來,還點瀏覽器上

            posted @ 2012-12-20 14:47 tqsheng 閱讀(226) | 評論 (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 閱讀(5240) | 評論 (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 閱讀(225) | 評論 (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 閱讀(5307) | 評論 (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 閱讀(146) | 評論 (0)編輯 收藏

            vlc

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

            posted @ 2012-12-10 17:16 tqsheng 閱讀(174) | 評論 (0)編輯 收藏

            霧化器

            天卓睿豐醫療器械(北京)有限公司
            地址:北京市東城區東直門外大街48號東方銀座寫字樓25L室
            郵編:100027
            電話:01084477765 
            傳真:01084477289

            免費咨詢電話:800 810 1906(工作日9:00至18:00開通,僅座機支持)

            posted @ 2012-12-08 21:55 tqsheng 閱讀(205) | 評論 (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 閱讀(352) | 評論 (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 閱讀(5468) | 評論 (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(超文本傳輸協議)是一個基于請求與響應模式的、無狀態的、應用層的協議,?;赥CP的連接方式,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 閱讀(366) | 評論 (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??蓤绦谐绦蛭募须m然沒有長度非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 閱讀(264) | 評論 (0)編輯 收藏

            僅列出標題
            共25頁: 1 2 3 4 5 6 7 8 9 Last 
            97久久国产亚洲精品超碰热| 久久99免费视频| 亚洲综合久久综合激情久久| 一本久久a久久精品vr综合| 久久性生大片免费观看性| 国产福利电影一区二区三区久久久久成人精品综合 | 人妻少妇精品久久| 久久久久久久综合综合狠狠| 99久久99久久精品国产片果冻| 99精品国产在热久久| 国产精品免费看久久久| 99久久成人国产精品免费| 久久久久中文字幕| 国产激情久久久久影院小草| 夜夜亚洲天天久久| A级毛片无码久久精品免费| 99久久综合狠狠综合久久| 久久99精品免费一区二区| 国产精久久一区二区三区| 中文字幕久久欲求不满| 国产91色综合久久免费分享| 精品久久777| 久久99国产一区二区三区| 亚洲午夜福利精品久久| 久久久SS麻豆欧美国产日韩| 久久天天婷婷五月俺也去| 天天躁日日躁狠狠久久| 国产精品99久久精品| 日本加勒比久久精品| 色综合久久综合中文综合网| 免费精品99久久国产综合精品| 国产精品无码久久久久| 一个色综合久久| 国内精品久久人妻互换| 2021国产成人精品久久| 色悠久久久久久久综合网| 亚洲乱码中文字幕久久孕妇黑人| 2021少妇久久久久久久久久| 欧美日韩精品久久久久| 久久久精品国产sm调教网站| 久久国产视屏|