網(wǎng)站首頁
分類導(dǎo)航
試題中心
下載中心
英語學(xué)習(xí)
繽紛校園
考試論壇
網(wǎng)站留言
客服中心
 匯編語言教程之四
【字體:
匯編語言教程之四
http://m.1glr.cn 來源:考試吧(Exam8.com) 點(diǎn)擊: 更新:2004-8-16

第三章 操作內(nèi)存

在前面的章節(jié)中,我們已經(jīng)了解了寄存器的基本使用方法。而正如結(jié)尾提到的那樣,僅僅使用寄存器做一點(diǎn)運(yùn)算是沒有什么太大意義的,畢竟它們不能保存太多的數(shù)據(jù),因此,對編程人員而言,他肯定迫切地希望訪問內(nèi)存,以保存更多的數(shù)據(jù)。

我將分別介紹如何在保護(hù)模式和實(shí)模式操作內(nèi)存,然而在此之前,我們先熟悉一下這兩種模式中內(nèi)存的結(jié)構(gòu)。

3.1 實(shí)模式

事實(shí)上,在實(shí)模式中,內(nèi)存比保護(hù)模式中的結(jié)構(gòu)更令人困惑。內(nèi)存被分割成段,并且,操作內(nèi)存時(shí),需要指定段和偏移量。不過,理解這些概念是非常容易的事情。請看下面的圖:

段-寄存器這種格局是早期硬件電路限制留下的一個(gè)傷疤。地址總線在當(dāng)時(shí)有20-bit。

然而20-bit的地址不能放到16-bit的寄存器里,這意味著有4-bit必須放到別的地方。因此,為了訪問所有的內(nèi)存,必須使用兩個(gè)16-bit寄存器。

這一設(shè)計(jì)上的折衷方案導(dǎo)致了今天的段-偏移量格局。最初的設(shè)計(jì)中,其中一個(gè)寄存器只有4-bit有效,然而為了簡化程序,兩個(gè)寄存器都是16-bit有效,并在執(zhí)行時(shí)求出加權(quán)和來標(biāo)識20-bit地址。

偏移量是16-bit的,因此,一個(gè)段是64KB。下面的圖可以幫助你理解20-bit地址是如何形成的:

段-偏移量標(biāo)識的地址通常記做 段:偏移量 的形式。

由于這樣的結(jié)構(gòu),一個(gè)內(nèi)存有多個(gè)對應(yīng)的地址。例如,0000:0010和0001:0000指的是同一內(nèi)存地址。又如,

0000:1234 = 0123:0004 = 0120:0034 = 0100:0234
0001:1234 = 0124:0004 = 0120:0044 = 0100:0244

作為負(fù)面影響之一,在段上加1相當(dāng)于在偏移量上加16,而不是一個(gè)“全新”的段。反之,在偏移量上加16也和在段上加1等價(jià)。某些時(shí)候,據(jù)此認(rèn)為段的“粒度”是16字節(jié)。

練習(xí)題
嘗試一下將下面的地址轉(zhuǎn)化為20bit的地址:

2EA8:D678 26CF:8D5F 453A:CFAD 2933:31A6 5924:DCCF
694E:175A 2B3C:D218 728F:6578 68E1:A7DC 57EC:AEEA

稍高一些的要求是,寫一個(gè)程序?qū)⒍螢锳X、偏移量為BX的地址轉(zhuǎn)換為20bit的地址,并保存于EAX中。

[上面習(xí)題的答案]

我們現(xiàn)在可以寫一個(gè)真正的程序了。

經(jīng)典程序:Hello, world

;;; 應(yīng)該得到一個(gè)29字節(jié)的.com文件

.MODEL TINY
.CODE

CR equ 13
LF equ 10
TERMINATOR equ '$'

ORG 100h

Main PROC
mov dx,offset sMessage
mov ah,9
int 21h
mov ax,4c00h
int 21h
Main ENDP

sMessage:
  DB 'Hello, World!'
  DB CR,LF,TERMINATOR

END Main


; .COM文件的內(nèi)存模型是‘TINY’
; 代碼段開始

; 回車
; 換行
; DOS字符串結(jié)束符

; 代碼起始地址為CS:0100h


; 令DS:DX指向Message
; int 21h(DOS中斷)功能9 -
; 顯示字符串到標(biāo)準(zhǔn)輸出設(shè)備
; int 21h功能4ch -
; 終止程序并返回AL的錯(cuò)誤代碼
 

 


; 程序結(jié)束的同時(shí)指定入口點(diǎn)為Main

那么,我們需要解釋很多東西。

首先,作為匯編語言的抽象,C語言擁有“指針”這個(gè)數(shù)據(jù)類型。在匯編語言中,幾乎所有對內(nèi)存的操作都是由對給定地址的內(nèi)存進(jìn)行訪問來完成的。這樣,在匯編語言中,絕大多數(shù)操作都要和指針產(chǎn)生或多或少的聯(lián)系。

這里我想強(qiáng)調(diào)的是,由于這一特性,匯編語言中同樣會(huì)出現(xiàn)C程序中常見的緩沖區(qū)溢出問題。如果你正在設(shè)計(jì)一個(gè)與安全有關(guān)的系統(tǒng),那么最好是仔細(xì)檢查你用到的每一個(gè)串,例如,它們是否一定能夠以你預(yù)期的方式結(jié)束,以及(如果使用的話)你的緩沖區(qū)是否能保證實(shí)際可能輸入的數(shù)據(jù)不被寫入到它以外的地方。作為一個(gè)匯編語言程序員,你有義務(wù)檢查每一行代碼的可用性。

程序中的equ偽指令是宏匯編特有的,它的意思接近于C或Pascal中的const(常量)。多數(shù)情況下,equ偽指令并不為符號分配空間。

此外,匯編程序執(zhí)行一項(xiàng)操作是非常繁瑣的,通常,在對與效率要求不高的地方,我們習(xí)慣使用系統(tǒng)提供的中斷服務(wù)來完成任務(wù)。例如本例中的中斷21h,它是DOS時(shí)代的中斷服務(wù),在Windows中,它也被認(rèn)為是Windows API的一部分(這一點(diǎn)可以在Microsoft的文檔中查到)。中斷可以被理解為高級語言中的子程序,但又不完全一樣——中斷使用系統(tǒng)棧來保存當(dāng)前的機(jī)器狀態(tài),可以由硬件發(fā)起,通過修改機(jī)器狀態(tài)字來反饋信息,等等。

那么,最后一段通過DB存放的數(shù)據(jù)到底保存在哪里了呢?答案是緊挨著代碼存放。在匯編語言中,DB和普通的指令的地位是相同的。如果你的匯編程序并不知道新的助記符(例如,新的處理器上的CPUID指令),而你很清楚,那么可以用DB 機(jī)器碼的方式強(qiáng)行寫下指令。這意味著,你可以超越匯編器的能力撰寫匯編程序,然而,直接用機(jī)器碼編程是幾乎肯定是一件費(fèi)力不討好的事——匯編器廠商會(huì)經(jīng)常更新它所支持的指令集以適應(yīng)市場需要,而且,你可以期待你的匯編其能夠產(chǎn)生正確的代碼,因?yàn)闄C(jī)器查表是不會(huì)出錯(cuò)的。既然機(jī)器能夠幫我們做將程序轉(zhuǎn)換為代碼這件事情,那么為什么不讓它來做呢?

細(xì)心的讀者不難發(fā)現(xiàn),在程序中我們沒有對DS進(jìn)行賦值。那么,這是否意味著程序的結(jié)果將是不可預(yù)測的呢?答案是否定的。DOS(或Windows中的MS-DOS VM)在加載.com文件的時(shí)候,會(huì)對寄存器進(jìn)行很多初始化。.com文件被限制為小于64KB,這樣,它的代碼段、數(shù)據(jù)段都被裝入同樣的數(shù)值(即,初始狀態(tài)下DS=CS)。

也許會(huì)有人說,“嘿,這聽起來不太好,一個(gè)64KB的程序能做得了什么呢?還有,你吹得天花亂墜的堆棧段在什么地方?”那么,我們來看看下面這個(gè)新的Hello world程序,它是一個(gè)EXE文件,在DOS實(shí)模式下運(yùn)行。

;;; 應(yīng)該得到一個(gè)561 字節(jié)的EXE文件

.MODEL SMALL
.STACK 200h

CR equ 13
LF equ 10
TERMINATOR equ '$'

.DATA

Message DB 'Hello, World !'
  DB CR,LF,TERMINATOR

.CODE

Main PROC
mov ax, DGROUP
mov ds, ax

mov dx, offset Message
mov ah, 9
int 21h

mov ax, 4c00h
int 21h
Main ENDP

END main
 

; 采用“SMALL”內(nèi)存模型
; 堆棧段

; 回車
; 換行
; DOS字符串結(jié)束符

; 定義數(shù)據(jù)段

; 定義顯示串

; 定義代碼段


; 將數(shù)據(jù)段
; 加載到DS寄存器

; 設(shè)置DX
; 顯示


; 終止程序

561字節(jié)?實(shí)現(xiàn)相同功能的程序大了這么多!為什么呢?我們看到,程序擁有了完整的堆棧段、數(shù)據(jù)段、代碼段,其中堆棧段足足占掉了512字節(jié),其余的基本上沒什么變化。

分成多個(gè)段有什么好處呢?首先,它讓程序顯得更加清晰——你肯定更愿意看一個(gè)結(jié)構(gòu)清楚的程序,代碼中hard-coded的字符串、數(shù)據(jù)讓人覺得費(fèi)解。比如,mov dx, 0152h肯定不如mov dx, offset Message來的親切。此外,通過分段你可以使用更多的內(nèi)存,比如,代碼段騰出的空間可以做更多的事情。exe文件另一個(gè)吸引人的地方是它能夠?qū)崿F(xiàn)“重定位”,F(xiàn)在你不需要指定程序入口點(diǎn)的地址了,因?yàn)橄到y(tǒng)會(huì)找到你的程序入口點(diǎn),而不是死板的100h。

程序中的符號也會(huì)在系統(tǒng)加載的時(shí)候重新賦予新的地址。exe程序能夠保證你的設(shè)計(jì)容易地被實(shí)現(xiàn),不需要考慮太多的細(xì)節(jié)。

當(dāng)然,我們的主要目的是將匯編語言作為高級語言的一個(gè)有用的補(bǔ)充。如我在開始提到的那樣,真正完全用匯編語言實(shí)現(xiàn)的程序不一定就好,因?yàn)樗槐阌诰S護(hù),而且,由于結(jié)構(gòu)的原因,你也不太容易確保它是正確的;匯編語言是一種非結(jié)構(gòu)化的語言,調(diào)試一個(gè)精心設(shè)計(jì)的匯編語言程序,即使對于一個(gè)老手來說也不啻是一場惡夢,因?yàn)槟愫芸赡艿舻絼e人預(yù)設(shè)的“陷阱”中——這些技巧確實(shí)提高了代碼性能,然而你很可能不理解它,于是你把它改掉,接著就發(fā)現(xiàn)程序徹底敗掉了。使用匯編語言加強(qiáng)高級語言程序時(shí),你要做的通常只是使用匯編指令,而不必搭建完整的匯編程序。絕大多數(shù)(也是目前我遇到的全部)C/C++編譯器都支持內(nèi)嵌匯編,即在程序中使用匯編語言,而不必撰寫單獨(dú)的匯編語言程序——這可以節(jié)省你的不少精力,因?yàn)榍懊嬷v述的那些偽指令,如equ等,都可以用你熟悉的高級語言方式來編寫,編譯器會(huì)把它轉(zhuǎn)換為適當(dāng)?shù)男问健?/P>

需要說明的是,在高級語言中一定要注意編譯結(jié)果。編譯器會(huì)對你的匯編程序做一些修改,這不一定符合你的要求(附帶說一句,有時(shí)編譯器會(huì)很聰明地調(diào)整指令順序來提高性能,這種情況下最好測試一下哪種寫法的效果更好),此時(shí)需要做一些更深入的修改,或者用db來強(qiáng)制編碼。

3.2 保護(hù)模式

實(shí)模式的東西說得太多了,盡管我已經(jīng)刪掉了許多東西,并把一些原則性的問題拿到了這一節(jié)討論。這樣做不是沒有理由的——保護(hù)模式才是現(xiàn)在的程序(除了操作系統(tǒng)的底層啟動(dòng)代碼)最常用的CPU模式。保護(hù)模式提供了很多令人耳目一新的功能,包括內(nèi)存保護(hù)(這是保護(hù)模式這個(gè)名字的來源)、進(jìn)程支持、更大的內(nèi)存支持,等等。

對于一個(gè)編程人員來說,能“偷懶”是一件令人愉快的事情。這里“偷懶”是說把“應(yīng)該”由系統(tǒng)做的事情做的事情全都交給系統(tǒng)。為什么呢?這出自一個(gè)基本思想——人總有犯錯(cuò)誤的時(shí)候,然而規(guī)則不會(huì),正確地了解規(guī)則之后,你可以期待它像你所了解的那樣執(zhí)行。對于C程序來說,你自己用C語言寫的實(shí)現(xiàn)相同功能的函數(shù)通常沒有系統(tǒng)提供的函數(shù)性能好(除非你用了比函數(shù)庫好很多的算法),因?yàn)橄到y(tǒng)的函數(shù)往往使用了更好的優(yōu)化,甚至可能不是用C語言直接編寫的。

當(dāng)然,“偷懶”的意思是說,把那些應(yīng)該讓機(jī)器做的事情交給計(jì)算機(jī)來做,因?yàn)樗龅酶。我們?yīng)該把精力集中到設(shè)計(jì)算法,而不是編寫源代碼本身上,因?yàn)榫幾g器幾乎只能做等價(jià)優(yōu)化,而實(shí)現(xiàn)相同功能,但使用更好算法的程序?qū)崿F(xiàn),則幾乎只能由人自己完成。

舉個(gè)例子,這樣一個(gè)函數(shù):

int fun(){
  int a=0;
  register int i;
  for(i=0; i<1000; i++) a+=i;
  return a;
}

在某種編譯模式[DEBUG]下被編譯為

push ebp
mov ebp,esp
sub esp,48h
push ebx
push esi
push edi
lea edi,[ebp-48h]
mov ecx,12h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
mov dword ptr [ebp-4],0
mov dword ptr [ebp-8],0
jmp fun+31h
mov eax,dword ptr [ebp-8]
add eax,1
mov dword ptr [ebp-8],eax
cmp dword ptr [ebp-8],3E8h
jge fun+45h
mov ecx,dword ptr [ebp-4]
add ecx,dword ptr [ebp-8]
mov dword ptr [ebp-4],ecx
jmp fun+28h
mov eax,dword ptr [ebp-4]
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
; 子程序入口


; 保護(hù)現(xiàn)場


; 初始化變量-調(diào)試版本特有。
; 本質(zhì)是在堆中挖一塊地兒,存CCCCCCCC。
; 用串操作進(jìn)行,這將發(fā)揮Intel處理器優(yōu)勢
; ‘a(chǎn)=0’
; ‘i=0’

; 走著
; i++


; i<1000?


; a+=i;

; return a;

; 恢復(fù)現(xiàn)場

 

; 返回

而在另一種模式[RELEASE/MINSIZE]下卻被編譯為

xor eax,eax
xor ecx,ecx
add eax,ecx
inc ecx
cmp ecx,3E8h
jl fun+4
ret

; a=0;
; i=0;
; a+=i;
; i++;
; i<1000?
; 是->繼續(xù)繼續(xù)
; return a

如果讓我來寫,多半會(huì)寫成

mov eax, 079f2ch
ret

; return 499500

為什么這樣寫呢?我們看到,i是一個(gè)外界不能影響、也無法獲知的內(nèi)部狀態(tài)量。作為這段程序來說,對它的計(jì)算對于結(jié)果并沒有直接的影響——它的存在不過是方便算法描述而已。并且我們看到的,這段程序?qū)嶋H上無論執(zhí)行多少次,其結(jié)果都不會(huì)發(fā)生變化,因此,直接返回計(jì)算結(jié)果就可以了,計(jì)算是多余的(如果說一定要算,那么應(yīng)該是編譯器在編譯過程中完成它)。

更進(jìn)一步,我們甚至希望編譯器能夠直接把這個(gè)函數(shù)變成一個(gè)符號常量,這樣連操作堆棧的過程也省掉了。

第三種結(jié)果屬于“等效”代碼,而不是“等價(jià)”代碼。作為用戶,很多時(shí)候是希望編譯器這樣做的,然而由于目前的技術(shù)尚不成熟,有時(shí)這種做法會(huì)造成一些問題(gcc和g++的頂級優(yōu)化可以造成編譯出的FreeBSD內(nèi)核行為異常,這是我在FreeBSD上遇到的唯一一次軟件原因的kernel panic),因此,并不是所有的編譯器都這樣做(另一方面的原因是,如果編譯器在這方面做的太過火,例如自動(dòng)求解全部“固定”問題,那么如果你的程序是解決固定的問題“很大”,如求解迷宮,那么在編譯過程中你就會(huì)找錘子來砸計(jì)算機(jī)了)。然而,作為編譯器制造商,為了提高自己的產(chǎn)品的競爭力,往往會(huì)使用第三種代碼來做函數(shù)庫。正如前面所提到的那樣,這種優(yōu)化往往不是編譯器本身的作用,盡管現(xiàn)代編譯程序擁有編譯執(zhí)行、循環(huán)代碼外提、無用代碼去除等諸多優(yōu)化功能,但它都不能保證程序最優(yōu)。最后一種代碼恐怕很少有編譯器能夠做到,不信你可以用自己常用的編譯器加上各種優(yōu)化選項(xiàng)試試:)

發(fā)現(xiàn)什么了嗎?三種代碼中,對于內(nèi)存的訪問一個(gè)比一個(gè)少。這樣做的理由是,盡可能地利用寄存器并減少對內(nèi)存的訪問,可以提高代碼性能。在某些情況下,使代碼既小又快是可能的。

書歸正傳,我們來說說保護(hù)模式的內(nèi)存模型。保護(hù)模式的內(nèi)存和實(shí)模式有很多共同之處。

毫無疑問,以'protected mode'(保護(hù)模式), 'global descriptor table'(全局描述符表), 'local descriptor table'(本地描述符表)和'selector'(選擇器)搜索,你會(huì)得到完整介紹它們的大量信息。

保護(hù)模式與實(shí)模式的內(nèi)存類似,然而,它們之間最大的區(qū)別就是保護(hù)模式的內(nèi)存是“線性”的。

新的計(jì)算機(jī)上,32-bit的寄存器已經(jīng)不是什么新鮮事(如果你哪天聽說你的CPU的寄存器不是32-bit的,那么它——簡直可以肯定地說——的字長要比32-bit還要多。新的個(gè)人機(jī)上已經(jīng)開始逐步采用64-bit的CPU了),換言之,實(shí)際上段/偏移量這一格局已經(jīng)不再需要了。盡管如此,在繼續(xù)看保護(hù)模式內(nèi)存結(jié)構(gòu)時(shí),仍請記住段/偏移量的概念。不妨把段寄存器看作對于保護(hù)模式中的選擇器的一個(gè)模擬。選擇器是全局描述符表(Global Descriptor Table, GDT)或本地描述符表(Local Descriptor Table, LDT)的一個(gè)指針。

如圖所示,GDT和LDT的每一個(gè)項(xiàng)目都描述一塊內(nèi)存。例如,一個(gè)項(xiàng)目中包含了某塊被描述的內(nèi)存的物理的基地址、長度,以及其他一些相關(guān)信息。

保護(hù)模式是一個(gè)非常重要的概念,同時(shí)也是目前撰寫應(yīng)用程序時(shí),最常用的CPU模式(運(yùn)行在新的計(jì)算機(jī)上的操作系統(tǒng)很少有在實(shí)模式下運(yùn)行的)。

為什么叫保護(hù)模式呢?它“保護(hù)”了什么?答案是進(jìn)程的內(nèi)存。保護(hù)模式的主要目的在于允許多個(gè)進(jìn)程同時(shí)運(yùn)行,并保護(hù)它們的內(nèi)存不受其他進(jìn)程的侵犯。這有點(diǎn)類似于C++中的機(jī)制,然而它的強(qiáng)制力要大得多。如果你的進(jìn)程在保護(hù)模式下以不恰當(dāng)?shù)姆绞皆L問了內(nèi)存(例如,寫了“只讀”內(nèi)存,或讀了不可讀的內(nèi)存,等等),那么CPU就會(huì)產(chǎn)生一個(gè)異常。這個(gè)異常將交給操作系統(tǒng)處理,而這種處理,假如你的程序沒有特別說明操作系統(tǒng)該如何處理的話,一般就是殺掉做錯(cuò)了事情的進(jìn)程。

我像這樣的對話框大家一定非常熟悉(臨時(shí)寫了一個(gè)程序故意造成的錯(cuò)誤):

好的,只是一個(gè)程序崩潰了,而操作系統(tǒng)的其他進(jìn)程照常運(yùn)行(同樣的程序在DOS中幾乎是板上釘釘?shù)乃罊C(jī),因?yàn)镹ULL指針的位置恰好是中斷向量表),你甚至還可以調(diào)試它。

保護(hù)模式還有其他很多好處,在此就不一一贅述了。實(shí)模式和保護(hù)模式之間的切換問題我打算放在后面的“高級技巧”一章來講,因?yàn)槎鄶?shù)程序并不涉及這個(gè)。

了解了內(nèi)存的格局,我們就可以進(jìn)入下一節(jié)——操作內(nèi)存了。

3.3 操作內(nèi)存

前兩節(jié)中,我們介紹了實(shí)模式和保護(hù)模式中使用的不同的內(nèi)存格局,F(xiàn)在開始解釋如何使用這些知識。

回憶一下前面我們說過的,寄存器可以用作內(nèi)存指針。現(xiàn)在,是他們發(fā)揮作用的時(shí)候了。

可以將內(nèi)存想象為一個(gè)順序的字節(jié)流。使用指針,可以任意地操作(讀寫)內(nèi)存。

現(xiàn)在我們需要一些其他的指令格式來描述對于內(nèi)存的操作。操作內(nèi)存時(shí),首先需要的就是它的地址。

讓我們來看看下面的代碼:

mov ax,[0]

方括號表示,里面的表達(dá)式指定的不是立即數(shù),而是偏移量。在實(shí)模式中,DS:0中的那個(gè)字(16-bit長)將被裝入AX。

然而0是一個(gè)常數(shù),如果需要在運(yùn)行的時(shí)候加以改變,就需要一些特殊的技巧,比如程序自修改。匯編支持這個(gè)特性,然而我個(gè)人并不推薦這種方法——自修改大大降低程序的可讀性,并且還降低穩(wěn)定性,性能還不一定好。我們需要另外的技術(shù)。

mov bx,0
mov ax,[bx]

看起來舒服了一些,不是嗎?BX寄存器的內(nèi)容可以隨時(shí)更改,而不需要用冗長的代碼去修改自身,更不用擔(dān)心由此帶來的不穩(wěn)定問題。

同樣的,mov指令也可以把數(shù)據(jù)保存到內(nèi)存中:

mov [0],ax

在存儲(chǔ)器與寄存器之間交換數(shù)據(jù)應(yīng)該足夠清楚了。

有些時(shí)候我們會(huì)需要操作符來描述內(nèi)存數(shù)據(jù)的寬度:

操作符 意義
byte ptr 一個(gè)字節(jié)(8-bit, 1 byte)
word ptr 一個(gè)字(16-bit)
dword ptr 一個(gè)雙字(32-bit)

例如,在DS:100h處保存1234h,以字存放:

mov word ptr [100h],01234h

于是我們將mov指令擴(kuò)展為:

mov reg(8,16,32), mem(8,16,32)
mov mem(8,16,32), reg(8,16,32)
mov mem(8,16,32), imm(8,16,32)

需要說明的是,加減同樣也可以在[]中使用,例如:

mov ax,[bx+10]
mov ax,[bx+si]
mov ax,es:[di+bp]

等等。我們看到,對于內(nèi)存的操作,即使使用MOV指令,也有許多種可能的方式。下一節(jié)中,我們將介紹如何操作串。

文章錄入:蕭雨    責(zé)任編輯:admin  
  • 上一篇文章:

  • 下一篇文章:
  •  版權(quán)聲明
       如果本網(wǎng)站所轉(zhuǎn)載內(nèi)容不慎侵犯了您的權(quán)益,請與我們聯(lián)系,我們將會(huì)及時(shí)處理。如轉(zhuǎn)載本網(wǎng)內(nèi)容,請注明出處。
     發(fā)表評論
    關(guān)于本站 網(wǎng)站聲明 廣告服務(wù)  聯(lián)系方式  付款方式  站內(nèi)導(dǎo)航  客服中心  友情鏈接   
    Copyright © 2004-2006 考試吧 (Exam8.com) All Rights Reserved 
    中國科學(xué)院研究生院中關(guān)村園區(qū)(北京市海淀區(qū))