导读:
十分感谢mik的帮助。问题找到了。
LDT为系统段,我把它设为数据段,结果不行了。
Intel手册中没说,我以为两个都行的都行的。
————————————————————————————————————————————
我的VM乱报错
,以后我可不相信它了。
结果让我绕了好大弯子,什么ss段出错,什么ds段不对。
我吐血。发了我好多精力啊。
--------------------------------------------------------------------------------------------------------
PS:看了intel的sample,我才明白什么才叫assembler。那叫什么乱七八糟的代码。是人看的么??又是cp,又是builer分配的。我手上没有masm,也没有dos,分析目标代码都不行。
附上:intel的代码
[Copy to clipboard][-] CODE:
10.4.5 First Task
The initialization procedure can run awhile in protected mode without
initializing the task register; however, before the first task switch, the
following conditions must prevail:
● There must be a valid task state segment (TSS) for the new task. The
stack pointers in the TSS for privilege levels numerically less than or
equal to the initial CPL must point to valid stack segments.
● The task register must point to an area in which to save the current
task state. After the first task switch, the information dumped in this
area is not needed, and the area can be used for other purposes.
10.5 Initialization Example
$TITLE ('Initial Task')
NAME INIT
init_stack SEGMENT RW
DW 20 DUP(?)
tos LABEL WORD
init_stack ENDS
init_data SEGMENT RW PUBLIC
DW 20 DUP(?)
init_data ENDS
init_code SEGMENT ER PUBLIC
ASSUME DS:init_data
nop
nop
nop
init_start:
set up stack
mov ax, init_stack
mov ss, ax
mov esp, offset tos
mov a1,1
blink:
xor a1,1
out 0e4h,a1
mov cx,3FFFh
here:
dec cx
jnz here
jmp SHORT blink
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 179 of 421
hlt
init_code ends
END init_start, SS:init_stack, DS:init_data
$TITLE('Protected Mode Transition -- 386 initialization')
NAME RESET
*****************************************************************
Upon reset the 386 starts executing at address 0FFFFFFF0H. The
upper 12 address bits remain high until a FAR call or jump is
executed.
Assume the following:
- a short jump at address 0FFFFFFF0H (placed there by the
system builder) causes execution to begin at START in segment
RESET_CODE.
- segment RESET_CODE is based at physical address 0FFFF0000H,
i.e. at the start of the last 64K in the 4G address space.
Note that this is the base of the CS register at reset. If
you locate ROMcode above this address, you will need to
figure out an adjustment factor to address things within this
segment.
*****************************************************************
$EJECT ;
Define addresses to locate GDT and IDT in RAM.
These addresses are also used in the BLD386 file that defines
the GDT and IDT. If you change these addresses, make sure you
change the base addresses specified in the build file.
GDTbase EQU 00001000H ;physical address for GDT base
IDTbase EQU 00000400H ;physical address for IDT base
PUBLIC GDT_EPROM
PUBLIC IDT_EPROM
PUBLIC START
DUMMY segment rw ;ONLY for ASM386 main module stack init
DW 0
DUMMY ends
*****************************************************************
Note: RESET CODE must be USEl6 because the 386 initally executes
in real mode.
RESET_CODE segment er PUBLIC USE16
ASSUME DS:nothing, ES:nothing
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 180 of 421
386 Descriptor template
DESC STRUC
lim_0_15 DW 0 ;limit bits (0..15)
bas_0_15 DW 0 ;base bits (0..15)
bas_16_23 DB 0 ;base bits (16..23)
access DB 0 ;access byte
gran DB 0 ;granularity byte
bas_24_31 DB 0 ;base bits (24..31)
DESC ENDS
The following is the layout of the real GDT created by BLD386.
It is located in EPROM and will be copied to RAM.
GDT[O] ... NULL
GDT[1] ... Alias for RAM GDT
GDT[2] ... Alias for RAM IDT
GDT[2] ... initial task TSS
GDT[3] ... initial task TSS alias
GDT[4] ... initial task LDT
GDT[5] ... initial task LDT alias
define entries in GDT and IDT.
GDT_ENTRIES EQU 8
IDT_ENTRIES EQU 32
define some constants to index into the real GDT
GDT_ALIAS EQU 1*SIZE DESC
IDT_ALIAS EQU 2*SIZE DESC
INIT_TSS EQU 3*SIZE DESC
INIT_TSS_A EQU 4*SIZE DESC
INIT_LDT EQU 5*SIZE DESC
INIT_LDT_A EQU 6*SIZE DESC
location of alias in INIT_LDT
INIT_LDT_ALIAS EQU 1*SIZE DESC
access rights byte for DATA and TSS descriptors
DS_ACCESS EQU 010010010B
TSS_ACCESS EQU 010001001B
This temporary GDT will be used to set up the real GDT in RAM.
Temp_GDT LABEL BYTE ;tag for begin of scratch GDT
NULL_DES DESC <>;NULL descriptor
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 181 of 421
32-Gigabyte data segment based at 0
FLAT_DES DESC <0FFFFH,0,0,92h,0CFh,0>
GDT_eprom DP ? ;Builder places GDT address and limit
in this 6 byte area.
IDT_eprom DP ? ;Builder places IDT address and limit
in this 6 byte area.
Prepare operand for loadings GDTR and LDTR.
TGDT_pword LABEL PWORD ;for temp GDT
DW end_Temp_GDT_Temp_GDT -1
DD 0
GDT_pword LABEL PWORD ;for GDT in RAM
DW GDT_ENTRIES * SIZE DESC -1
DD GDTbase
IDT_pword LABEL PWORD ;for IDT in RAM
DW IDT_ENTRIES * SIZE DESC -1
DD IDTbase
end_Temp_GDT LABEL BYTE
Define equates for addressing convenience.
GDT_DES_FLAT EQU DS:GDT_ALIAS +GDTbase
IDT_DES_FLAT EQU DS:IDT_ALIAS +GDTbase
INIT_TSS_A_OFFSET EQU DS:INIT_TSS_A
INIT_TSS_OFFSET EQU DS:INIT_TSS
INIT_LDT_A_OFFSET EQU DS:INIT_LDT_A
INIT_LDT_OFFSET EQU DS:INIT_LDT
define pointer for first task switch
ENTRY POINTER LABEL DWORD
DW 0, INIT_TSS
******************************************************************
Jump from reset vector to here.
START:
CLI ;disable interrupts
CLD ;clear direction flag
LIDT NULL_des ;force shutdown on errors
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 182 of 421
move scratch GDT to RAM at physical 0
XOR DI,DI
MOV ES,DI ;point ES:DI to physical location 0
MOV SI,OFFSET Temp_GDT
MOV CX,end_Temp_GDT-Temp_GDT ;set byte count
INC CX
move table
REP MOVS BYTE PTR ES:[DI],BYTE PTR CS:[SI]
LGDT tGDT_pword ;load GDTR for Temp. GDT
(located at 0)
switch to protected mode
MOV EAX,CR0 ;get current CRO
MOV EAX,1 ;set PE bit
MOV CRO,EAX ;begin protected mode
clear prefetch queue
JMP SHORT flush
flush:
set DS,ES,SS to address flat linear space (0 ... 4GB)
MOV BX,FLAT_DES-Temp_GDT
MOV US,BX
MOV ES,BX
MOV SS,BX
initialize stack pointer to some (arbitrary) RAM location
MOV ESP, OFFSET end_Temp_GDT
copy eprom GDT to RAM
MOV ESI,DWORD PTR GDT_eprom +2 ;get base of eprom GDT
(put here by builder).
MOV EDI,GDTbase ;point ES:EDI to GDT base in RAM.
MOV CX,WORD PTR gdt_eprom +0 ;limit of eprom GDT
INC CX
SHR CX,1 ;easier to move words
CLD
REP MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
copy eprom IDT to RAM
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 183 of 421
MOV ESI,DWORD PTR IDT_eprom +2 ;get base of eprom IDT
(put here by builder)
MOV EDI,IDTbase ;point ES:EDI to IDT base in RAM.
MOV CX,WORD PTR idt_eprom +0 ;limit of eprom IDT
INC CX
SHR CX,1
CLD
REP MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
switch to RAM GDT and IDT
LIDT IDT_pword
LGDT GDT_pword
MOV BX,GDT_ALIAS ;point DS to GDT alias
MOV DS,BX
copy eprom TSS to RAM
MOV BX,INIT_TSS_A ;INIT TSS A descriptor base
has RAM location of INIT TSS.
MOV ES,BX ;ES points to TSS in RAM
MOV BX,INIT_TSS ;get inital task selector
LAR DX,BX ;save access byte
MOV [BX].access,DS_ACCESS ;set access as data segment
MOV FS,BX ;FS points to eprom TSS
XOR si,si ;FS:si points to eprom TSS
XOR di,di ;ES:di points to RAM TSS
MOV CX,[BX].lim_0_15 ;get count to move
INC CX
move INIT_TSS to RAM.
REP MOVS BYTE PTR ES:[di],BYTE PTR FS:[si]
MOV [BX].access,DH ;restore access byte
change base of INIT TSS descriptor to point to RAM.
MOV AX,INIT_TSS_A_OFFSET.bas_0_15
MOV INIT_TSS_OFFSET.bas_0_15,AX
MOV AL,INIT_TSS_A_OFFSET.bas_16_23
MOV INIT_TSS_OFFSET.bas_16_23,AL
MOV AL,INIT_TSS_A_OFFSET.bas_24_31
MOV INIT_TSS_OFFSET.bas_24_31,AL
change INIT TSS A to form a save area for TSS on first task
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 184 of 421
switch. Use RAM at location 0.
MOV BX,INIT_TSS_A
MOV WORD PTR [BX].bas_0_15,0
MOV [BX].bas_16_23,0
MOV [BX].bas_24_31,0
MOV [BX].access,TSS_ACCESS
MOV [BX].gran,O
LTR BX ;defines save area for TSS
copy eprom LDT to RAM
MOV BX,INIT_LDT_A ;INIT_LDT_A descriptor has
base address in RAM for INIT_LDT.
MOV ES,BX ;ES points LDT location in RAM.
MOV AH,[BX].bas_24_31
MOV AL,[BX].bas_16_23
SHL EAX,16
MOV AX,[BX].bas_0_15 ;save INIT_LDT base (ram) in EAX
MOV BX,INIT_LDT ;get inital LDT selector
LAR DX,BX ;save access rights
MOV [BX].access,DS_ACCESS ;set access as data segment
MOV FS,BX ;FS points to eprom LDT
XOR si,si ;FS:SI points to eprom LDT
XOR di,di ;ES:DI points to RAM LDT
MOV CX,[BX].lim_0_15 ;get count to move
INC CX
move initial LDT to RAM
REP MOVS BYTE PTR ES:[di],BYTE PTR FS:[si]
MOV [BX].access,DH ;restore access rights in
INIT_LDT descriptor
change base of alias (of INIT_LDT) to point to location in RAM.
MOV ES:[INIT_LDT_ALIAS].bas_0_15,AX
SHR EAX,16
MOV ES:[INIT_LDT_ALIAS].bas_16_23,AL
MOV ES:[INIT_LDT_ALIAS].bas_24_31,AH
now set the base value in INIT_LDT descriptor
MOV AX,INIT_LDT_A_OFFSET.bas_0_15
MOV INIT_LDT_OFFSET.bas_0_15,AX
MOV AL,INIT_LDT_A_OFFSET.bas_16_23
MOV INIT_LDT_OFFSET.bas_16_23,AL
MOV AL,INIT_LDT_A_OFFSET.bas_24_31
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 185 of 421
MOV INIT_LDT_OFFSET.bas_24_31,AL
Now GDT, IDT, initial TSS and initial LDT are all set up.
Start the first task!
'
JMP ENTRY_POINTER
RESET_CODE ends
END START, SS:DUMMY,DS:DUMMY
本文转自
http://linux.chinaunix.net/bbs/viewthread.php?tid=907431&page=2
十分感谢mik的帮助。问题找到了。
LDT为系统段,我把它设为数据段,结果不行了。
Intel手册中没说,我以为两个都行的都行的。
————————————————————————————————————————————
我的VM乱报错
,以后我可不相信它了。
结果让我绕了好大弯子,什么ss段出错,什么ds段不对。
我吐血。发了我好多精力啊。
--------------------------------------------------------------------------------------------------------
PS:看了intel的sample,我才明白什么才叫assembler。那叫什么乱七八糟的代码。是人看的么??又是cp,又是builer分配的。我手上没有masm,也没有dos,分析目标代码都不行。
附上:intel的代码
[Copy to clipboard][-] CODE:
10.4.5 First Task
The initialization procedure can run awhile in protected mode without
initializing the task register; however, before the first task switch, the
following conditions must prevail:
● There must be a valid task state segment (TSS) for the new task. The
stack pointers in the TSS for privilege levels numerically less than or
equal to the initial CPL must point to valid stack segments.
● The task register must point to an area in which to save the current
task state. After the first task switch, the information dumped in this
area is not needed, and the area can be used for other purposes.
10.5 Initialization Example
$TITLE ('Initial Task')
NAME INIT
init_stack SEGMENT RW
DW 20 DUP(?)
tos LABEL WORD
init_stack ENDS
init_data SEGMENT RW PUBLIC
DW 20 DUP(?)
init_data ENDS
init_code SEGMENT ER PUBLIC
ASSUME DS:init_data
nop
nop
nop
init_start:
set up stack
mov ax, init_stack
mov ss, ax
mov esp, offset tos
mov a1,1
blink:
xor a1,1
out 0e4h,a1
mov cx,3FFFh
here:
dec cx
jnz here
jmp SHORT blink
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 179 of 421
hlt
init_code ends
END init_start, SS:init_stack, DS:init_data
$TITLE('Protected Mode Transition -- 386 initialization')
NAME RESET
*****************************************************************
Upon reset the 386 starts executing at address 0FFFFFFF0H. The
upper 12 address bits remain high until a FAR call or jump is
executed.
Assume the following:
- a short jump at address 0FFFFFFF0H (placed there by the
system builder) causes execution to begin at START in segment
RESET_CODE.
- segment RESET_CODE is based at physical address 0FFFF0000H,
i.e. at the start of the last 64K in the 4G address space.
Note that this is the base of the CS register at reset. If
you locate ROMcode above this address, you will need to
figure out an adjustment factor to address things within this
segment.
*****************************************************************
$EJECT ;
Define addresses to locate GDT and IDT in RAM.
These addresses are also used in the BLD386 file that defines
the GDT and IDT. If you change these addresses, make sure you
change the base addresses specified in the build file.
GDTbase EQU 00001000H ;physical address for GDT base
IDTbase EQU 00000400H ;physical address for IDT base
PUBLIC GDT_EPROM
PUBLIC IDT_EPROM
PUBLIC START
DUMMY segment rw ;ONLY for ASM386 main module stack init
DW 0
DUMMY ends
*****************************************************************
Note: RESET CODE must be USEl6 because the 386 initally executes
in real mode.
RESET_CODE segment er PUBLIC USE16
ASSUME DS:nothing, ES:nothing
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 180 of 421
386 Descriptor template
DESC STRUC
lim_0_15 DW 0 ;limit bits (0..15)
bas_0_15 DW 0 ;base bits (0..15)
bas_16_23 DB 0 ;base bits (16..23)
access DB 0 ;access byte
gran DB 0 ;granularity byte
bas_24_31 DB 0 ;base bits (24..31)
DESC ENDS
The following is the layout of the real GDT created by BLD386.
It is located in EPROM and will be copied to RAM.
GDT[O] ... NULL
GDT[1] ... Alias for RAM GDT
GDT[2] ... Alias for RAM IDT
GDT[2] ... initial task TSS
GDT[3] ... initial task TSS alias
GDT[4] ... initial task LDT
GDT[5] ... initial task LDT alias
define entries in GDT and IDT.
GDT_ENTRIES EQU 8
IDT_ENTRIES EQU 32
define some constants to index into the real GDT
GDT_ALIAS EQU 1*SIZE DESC
IDT_ALIAS EQU 2*SIZE DESC
INIT_TSS EQU 3*SIZE DESC
INIT_TSS_A EQU 4*SIZE DESC
INIT_LDT EQU 5*SIZE DESC
INIT_LDT_A EQU 6*SIZE DESC
location of alias in INIT_LDT
INIT_LDT_ALIAS EQU 1*SIZE DESC
access rights byte for DATA and TSS descriptors
DS_ACCESS EQU 010010010B
TSS_ACCESS EQU 010001001B
This temporary GDT will be used to set up the real GDT in RAM.
Temp_GDT LABEL BYTE ;tag for begin of scratch GDT
NULL_DES DESC <>;NULL descriptor
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 181 of 421
32-Gigabyte data segment based at 0
FLAT_DES DESC <0FFFFH,0,0,92h,0CFh,0>
GDT_eprom DP ? ;Builder places GDT address and limit
in this 6 byte area.
IDT_eprom DP ? ;Builder places IDT address and limit
in this 6 byte area.
Prepare operand for loadings GDTR and LDTR.
TGDT_pword LABEL PWORD ;for temp GDT
DW end_Temp_GDT_Temp_GDT -1
DD 0
GDT_pword LABEL PWORD ;for GDT in RAM
DW GDT_ENTRIES * SIZE DESC -1
DD GDTbase
IDT_pword LABEL PWORD ;for IDT in RAM
DW IDT_ENTRIES * SIZE DESC -1
DD IDTbase
end_Temp_GDT LABEL BYTE
Define equates for addressing convenience.
GDT_DES_FLAT EQU DS:GDT_ALIAS +GDTbase
IDT_DES_FLAT EQU DS:IDT_ALIAS +GDTbase
INIT_TSS_A_OFFSET EQU DS:INIT_TSS_A
INIT_TSS_OFFSET EQU DS:INIT_TSS
INIT_LDT_A_OFFSET EQU DS:INIT_LDT_A
INIT_LDT_OFFSET EQU DS:INIT_LDT
define pointer for first task switch
ENTRY POINTER LABEL DWORD
DW 0, INIT_TSS
******************************************************************
Jump from reset vector to here.
START:
CLI ;disable interrupts
CLD ;clear direction flag
LIDT NULL_des ;force shutdown on errors
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 182 of 421
move scratch GDT to RAM at physical 0
XOR DI,DI
MOV ES,DI ;point ES:DI to physical location 0
MOV SI,OFFSET Temp_GDT
MOV CX,end_Temp_GDT-Temp_GDT ;set byte count
INC CX
move table
REP MOVS BYTE PTR ES:[DI],BYTE PTR CS:[SI]
LGDT tGDT_pword ;load GDTR for Temp. GDT
(located at 0)
switch to protected mode
MOV EAX,CR0 ;get current CRO
MOV EAX,1 ;set PE bit
MOV CRO,EAX ;begin protected mode
clear prefetch queue
JMP SHORT flush
flush:
set DS,ES,SS to address flat linear space (0 ... 4GB)
MOV BX,FLAT_DES-Temp_GDT
MOV US,BX
MOV ES,BX
MOV SS,BX
initialize stack pointer to some (arbitrary) RAM location
MOV ESP, OFFSET end_Temp_GDT
copy eprom GDT to RAM
MOV ESI,DWORD PTR GDT_eprom +2 ;get base of eprom GDT
(put here by builder).
MOV EDI,GDTbase ;point ES:EDI to GDT base in RAM.
MOV CX,WORD PTR gdt_eprom +0 ;limit of eprom GDT
INC CX
SHR CX,1 ;easier to move words
CLD
REP MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
copy eprom IDT to RAM
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 183 of 421
MOV ESI,DWORD PTR IDT_eprom +2 ;get base of eprom IDT
(put here by builder)
MOV EDI,IDTbase ;point ES:EDI to IDT base in RAM.
MOV CX,WORD PTR idt_eprom +0 ;limit of eprom IDT
INC CX
SHR CX,1
CLD
REP MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
switch to RAM GDT and IDT
LIDT IDT_pword
LGDT GDT_pword
MOV BX,GDT_ALIAS ;point DS to GDT alias
MOV DS,BX
copy eprom TSS to RAM
MOV BX,INIT_TSS_A ;INIT TSS A descriptor base
has RAM location of INIT TSS.
MOV ES,BX ;ES points to TSS in RAM
MOV BX,INIT_TSS ;get inital task selector
LAR DX,BX ;save access byte
MOV [BX].access,DS_ACCESS ;set access as data segment
MOV FS,BX ;FS points to eprom TSS
XOR si,si ;FS:si points to eprom TSS
XOR di,di ;ES:di points to RAM TSS
MOV CX,[BX].lim_0_15 ;get count to move
INC CX
move INIT_TSS to RAM.
REP MOVS BYTE PTR ES:[di],BYTE PTR FS:[si]
MOV [BX].access,DH ;restore access byte
change base of INIT TSS descriptor to point to RAM.
MOV AX,INIT_TSS_A_OFFSET.bas_0_15
MOV INIT_TSS_OFFSET.bas_0_15,AX
MOV AL,INIT_TSS_A_OFFSET.bas_16_23
MOV INIT_TSS_OFFSET.bas_16_23,AL
MOV AL,INIT_TSS_A_OFFSET.bas_24_31
MOV INIT_TSS_OFFSET.bas_24_31,AL
change INIT TSS A to form a save area for TSS on first task
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 184 of 421
switch. Use RAM at location 0.
MOV BX,INIT_TSS_A
MOV WORD PTR [BX].bas_0_15,0
MOV [BX].bas_16_23,0
MOV [BX].bas_24_31,0
MOV [BX].access,TSS_ACCESS
MOV [BX].gran,O
LTR BX ;defines save area for TSS
copy eprom LDT to RAM
MOV BX,INIT_LDT_A ;INIT_LDT_A descriptor has
base address in RAM for INIT_LDT.
MOV ES,BX ;ES points LDT location in RAM.
MOV AH,[BX].bas_24_31
MOV AL,[BX].bas_16_23
SHL EAX,16
MOV AX,[BX].bas_0_15 ;save INIT_LDT base (ram) in EAX
MOV BX,INIT_LDT ;get inital LDT selector
LAR DX,BX ;save access rights
MOV [BX].access,DS_ACCESS ;set access as data segment
MOV FS,BX ;FS points to eprom LDT
XOR si,si ;FS:SI points to eprom LDT
XOR di,di ;ES:DI points to RAM LDT
MOV CX,[BX].lim_0_15 ;get count to move
INC CX
move initial LDT to RAM
REP MOVS BYTE PTR ES:[di],BYTE PTR FS:[si]
MOV [BX].access,DH ;restore access rights in
INIT_LDT descriptor
change base of alias (of INIT_LDT) to point to location in RAM.
MOV ES:[INIT_LDT_ALIAS].bas_0_15,AX
SHR EAX,16
MOV ES:[INIT_LDT_ALIAS].bas_16_23,AL
MOV ES:[INIT_LDT_ALIAS].bas_24_31,AH
now set the base value in INIT_LDT descriptor
MOV AX,INIT_LDT_A_OFFSET.bas_0_15
MOV INIT_LDT_OFFSET.bas_0_15,AX
MOV AL,INIT_LDT_A_OFFSET.bas_16_23
MOV INIT_LDT_OFFSET.bas_16_23,AL
MOV AL,INIT_LDT_A_OFFSET.bas_24_31
INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986
Page 185 of 421
MOV INIT_LDT_OFFSET.bas_24_31,AL
Now GDT, IDT, initial TSS and initial LDT are all set up.
Start the first task!
'
JMP ENTRY_POINTER
RESET_CODE ends
END START, SS:DUMMY,DS:DUMMY
本文转自
http://linux.chinaunix.net/bbs/viewthread.php?tid=907431&page=2