Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
qemu
提交
bc8a22cc
Q
qemu
项目概览
openeuler
/
qemu
通知
10
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Q
qemu
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
bc8a22cc
编写于
3月 30, 2003
作者:
B
bellard
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
better vm86 support
git-svn-id:
svn://svn.savannah.nongnu.org/qemu/trunk@69
c046a42c-6fe2-441c-8c8c-71466251a162
上级
f631ef9b
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
239 addition
and
89 deletion
+239
-89
Changelog
Changelog
+33
-0
TODO
TODO
+3
-2
cpu-i386.h
cpu-i386.h
+18
-18
linux-user/main.c
linux-user/main.c
+167
-64
linux-user/qemu.h
linux-user/qemu.h
+1
-0
linux-user/signal.c
linux-user/signal.c
+12
-5
syscall-i386.h
syscall-i386.h
+5
-0
未找到文件。
Changelog
浏览文件 @
bc8a22cc
version 0.1.4:
- more accurate VM86 emulation (can launch small DOS 16 bit
executables in wine).
- fixed push/pop fs/gs
- added iret instruction.
version 0.1.3:
- S390 support (Ulrich Weigand)
- glibc 2.3.x compile fix (Ulrich Weigand)
- socketcall endian fix (Ulrich Weigand)
- struct sockaddr endian fix (Ulrich Weigand)
- sendmsg/recvmsg endian fix (Ulrich Weigand)
- execve endian fix (Ulrich Weigand)
- fdset endian fix (Ulrich Weigand)
- partial setsockopt syscall support (Ulrich Weigand)
- more accurate pushf/popf emulation
- first partial vm86() syscall support (can be used with runcom example).
- added bound, cmpxchg8b, cpuid instructions
- added 16 bit addressing support/override for string operations
- poll() fix
version 0.1.2:
- compile fixes
- xlat instruction
- xchg instruction memory lock
- added simple vm86 example (not working with QEMU yet). The 54 byte
DOS executable 'pi_10.com' program was released by Bertram
Felgenhauer (more information at http://www.boo.net/~jasonp/pipage.html).
version 0.1.1:
- glibc 2.2 compilation fixes
- added -s and -L options
- binary distribution of x86 glibc and wine
- big endian fixes in ELF loader and getdents.
version 0.1:
...
...
TODO
浏览文件 @
bc8a22cc
- fix thread locks
- fix thread stack liberation
- fix x86 stack allocation
- optimize translated cache chaining (DLL PLT-like system)
- more syscalls (in particular all 64 bit ones, IPCs, fix 64 bit
issues, fix 16 bit uid issues)
- finish signal handing (fp87 state, more siginfo conversions)
- verify thread support (clone() and various locks)
- vm86 syscall support
- overrides/16bit for string ops
- make it self runnable (use same trick as ld.so : include its own relocator and libc)
- improved 16 bit support
- fix FPU exceptions (in particular: gen_op_fpush not before mem load)
cpu-i386.h
浏览文件 @
bc8a22cc
...
...
@@ -68,24 +68,24 @@
#define VIP_MASK 0x00100000
#define ID_MASK 0x00200000
#define EXCP00_DIVZ
1
#define EXCP01_SSTP
2
#define EXCP02_NMI
3
#define EXCP03_INT3
4
#define EXCP04_INTO
5
#define EXCP05_BOUND
6
#define EXCP06_ILLOP
7
#define EXCP07_PREX
8
#define EXCP08_DBLE
9
#define EXCP09_XERR
10
#define EXCP0A_TSS 1
1
#define EXCP0B_NOSEG 1
2
#define EXCP0C_STACK 1
3
#define EXCP0D_GPF 1
4
#define EXCP0E_PAGE 1
5
#define EXCP10_COPR 1
7
#define EXCP11_ALGN 1
8
#define EXCP12_MCHK 1
9
#define EXCP00_DIVZ
0
#define EXCP01_SSTP
1
#define EXCP02_NMI
2
#define EXCP03_INT3
3
#define EXCP04_INTO
4
#define EXCP05_BOUND
5
#define EXCP06_ILLOP
6
#define EXCP07_PREX
7
#define EXCP08_DBLE
8
#define EXCP09_XERR
9
#define EXCP0A_TSS 1
0
#define EXCP0B_NOSEG 1
1
#define EXCP0C_STACK 1
2
#define EXCP0D_GPF 1
3
#define EXCP0E_PAGE 1
4
#define EXCP10_COPR 1
6
#define EXCP11_ALGN 1
7
#define EXCP12_MCHK 1
8
#define EXCP_INTERRUPT 256
/* async interruption */
...
...
linux-user/main.c
浏览文件 @
bc8a22cc
...
...
@@ -106,77 +106,172 @@ uint64_t gdt_table[6];
//#define DEBUG_VM86
static
inline
int
is_revectored
(
int
nr
,
struct
target_revectored_struct
*
bitmap
)
{
return
(
tswap32
(
bitmap
->
__map
[
nr
>>
5
])
>>
(
nr
&
0x1f
))
&
1
;
}
static
inline
uint8_t
*
seg_to_linear
(
unsigned
int
seg
,
unsigned
int
reg
)
{
return
(
uint8_t
*
)((
seg
<<
4
)
+
(
reg
&
0xffff
));
}
static
inline
void
pushw
(
CPUX86State
*
env
,
int
val
)
{
env
->
regs
[
R_ESP
]
=
(
env
->
regs
[
R_ESP
]
&
~
0xffff
)
|
((
env
->
regs
[
R_ESP
]
-
2
)
&
0xffff
);
*
(
uint16_t
*
)
seg_to_linear
(
env
->
segs
[
R_SS
],
env
->
regs
[
R_ESP
])
=
val
;
}
static
inline
unsigned
int
get_vflags
(
CPUX86State
*
env
)
{
unsigned
int
eflags
;
eflags
=
env
->
eflags
&
~
(
VM_MASK
|
RF_MASK
|
IF_MASK
);
if
(
eflags
&
VIF_MASK
)
eflags
|=
IF_MASK
;
return
eflags
;
}
void
save_v86_state
(
CPUX86State
*
env
)
{
TaskState
*
ts
=
env
->
opaque
;
#ifdef DEBUG_VM86
printf
(
"save_v86_state
\n
"
);
#endif
/* put the VM86 registers in the userspace register structure */
ts
->
target_v86
->
regs
.
eax
=
tswap32
(
env
->
regs
[
R_EAX
]);
ts
->
target_v86
->
regs
.
ebx
=
tswap32
(
env
->
regs
[
R_EBX
]);
ts
->
target_v86
->
regs
.
ecx
=
tswap32
(
env
->
regs
[
R_ECX
]);
ts
->
target_v86
->
regs
.
edx
=
tswap32
(
env
->
regs
[
R_EDX
]);
ts
->
target_v86
->
regs
.
esi
=
tswap32
(
env
->
regs
[
R_ESI
]);
ts
->
target_v86
->
regs
.
edi
=
tswap32
(
env
->
regs
[
R_EDI
]);
ts
->
target_v86
->
regs
.
ebp
=
tswap32
(
env
->
regs
[
R_EBP
]);
ts
->
target_v86
->
regs
.
esp
=
tswap32
(
env
->
regs
[
R_ESP
]);
ts
->
target_v86
->
regs
.
eip
=
tswap32
(
env
->
eip
);
ts
->
target_v86
->
regs
.
cs
=
tswap16
(
env
->
segs
[
R_CS
]);
ts
->
target_v86
->
regs
.
ss
=
tswap16
(
env
->
segs
[
R_SS
]);
ts
->
target_v86
->
regs
.
ds
=
tswap16
(
env
->
segs
[
R_DS
]);
ts
->
target_v86
->
regs
.
es
=
tswap16
(
env
->
segs
[
R_ES
]);
ts
->
target_v86
->
regs
.
fs
=
tswap16
(
env
->
segs
[
R_FS
]);
ts
->
target_v86
->
regs
.
gs
=
tswap16
(
env
->
segs
[
R_GS
]);
ts
->
target_v86
->
regs
.
eflags
=
tswap32
(
env
->
eflags
);
/* restore 32 bit registers */
env
->
regs
[
R_EAX
]
=
ts
->
vm86_saved_regs
.
eax
;
env
->
regs
[
R_EBX
]
=
ts
->
vm86_saved_regs
.
ebx
;
env
->
regs
[
R_ECX
]
=
ts
->
vm86_saved_regs
.
ecx
;
env
->
regs
[
R_EDX
]
=
ts
->
vm86_saved_regs
.
edx
;
env
->
regs
[
R_ESI
]
=
ts
->
vm86_saved_regs
.
esi
;
env
->
regs
[
R_EDI
]
=
ts
->
vm86_saved_regs
.
edi
;
env
->
regs
[
R_EBP
]
=
ts
->
vm86_saved_regs
.
ebp
;
env
->
regs
[
R_ESP
]
=
ts
->
vm86_saved_regs
.
esp
;
env
->
eflags
=
ts
->
vm86_saved_regs
.
eflags
;
env
->
eip
=
ts
->
vm86_saved_regs
.
eip
;
cpu_x86_load_seg
(
env
,
R_CS
,
ts
->
vm86_saved_regs
.
cs
);
cpu_x86_load_seg
(
env
,
R_SS
,
ts
->
vm86_saved_regs
.
ss
);
cpu_x86_load_seg
(
env
,
R_DS
,
ts
->
vm86_saved_regs
.
ds
);
cpu_x86_load_seg
(
env
,
R_ES
,
ts
->
vm86_saved_regs
.
es
);
cpu_x86_load_seg
(
env
,
R_FS
,
ts
->
vm86_saved_regs
.
fs
);
cpu_x86_load_seg
(
env
,
R_GS
,
ts
->
vm86_saved_regs
.
gs
);
}
/* return from vm86 mode to 32 bit. The vm86() syscall will return
'retval' */
static
inline
void
return_to_32bit
(
CPUX86State
*
env
,
int
retval
)
{
#ifdef DEBUG_VM86
printf
(
"return_to_32bit: ret=0x%x
\n
"
,
retval
);
#endif
save_v86_state
(
env
);
env
->
regs
[
R_EAX
]
=
retval
;
}
/* handle VM86 interrupt (NOTE: the CPU core currently does not
support TSS interrupt revectoring, so this code is always executed) */
static
void
do_int
(
CPUX86State
*
env
,
int
intno
)
{
TaskState
*
ts
=
env
->
opaque
;
uint32_t
*
int_ptr
,
segoffs
;
if
(
env
->
segs
[
R_CS
]
==
TARGET_BIOSSEG
)
goto
cannot_handle
;
/* XXX: I am not sure this is really useful */
if
(
is_revectored
(
intno
,
&
ts
->
target_v86
->
int_revectored
))
goto
cannot_handle
;
if
(
intno
==
0x21
&&
is_revectored
((
env
->
regs
[
R_EAX
]
>>
8
)
&
0xff
,
&
ts
->
target_v86
->
int21_revectored
))
goto
cannot_handle
;
int_ptr
=
(
uint32_t
*
)(
intno
<<
2
);
segoffs
=
tswap32
(
*
int_ptr
);
if
((
segoffs
>>
16
)
==
TARGET_BIOSSEG
)
goto
cannot_handle
;
#ifdef DEBUG_VM86
printf
(
"VM86: emulating int 0x%x. CS:IP=%04x:%04x
\n
"
,
intno
,
segoffs
>>
16
,
segoffs
&
0xffff
);
#endif
/* save old state */
pushw
(
env
,
get_vflags
(
env
));
pushw
(
env
,
env
->
segs
[
R_CS
]);
pushw
(
env
,
env
->
eip
);
/* goto interrupt handler */
env
->
eip
=
segoffs
&
0xffff
;
cpu_x86_load_seg
(
env
,
R_CS
,
segoffs
>>
16
);
env
->
eflags
&=
~
(
VIF_MASK
|
TF_MASK
);
return
;
cannot_handle:
#ifdef DEBUG_VM86
printf
(
"VM86: return to 32 bits int 0x%x
\n
"
,
intno
);
#endif
return_to_32bit
(
env
,
TARGET_VM86_INTx
|
(
intno
<<
8
));
}
void
cpu_loop
(
struct
CPUX86State
*
env
)
{
int
er
r
;
int
trapn
r
;
uint8_t
*
pc
;
target_siginfo_t
info
;
for
(;;)
{
er
r
=
cpu_x86_exec
(
env
);
trapn
r
=
cpu_x86_exec
(
env
);
pc
=
env
->
seg_cache
[
R_CS
].
base
+
env
->
eip
;
switch
(
er
r
)
{
switch
(
trapn
r
)
{
case
EXCP0D_GPF
:
if
(
env
->
eflags
&
VM_MASK
)
{
TaskState
*
ts
;
int
ret
;
#ifdef DEBUG_VM86
printf
(
"VM86 exception %04x:%08x %02x
\n
"
,
env
->
segs
[
R_CS
],
env
->
eip
,
pc
[
0
]);
printf
(
"VM86 exception %04x:%08x %02x
%02x
\n
"
,
env
->
segs
[
R_CS
],
env
->
eip
,
pc
[
0
]
,
pc
[
1
]
);
#endif
/* VM86 mode */
ts
=
env
->
opaque
;
/* XXX: add all cases */
switch
(
pc
[
0
])
{
case
0xcd
:
/* int */
env
->
eip
+=
2
;
ret
=
TARGET_VM86_INTx
|
(
pc
[
1
]
<<
8
);
do_int
(
env
,
pc
[
1
]);
break
;
case
0x66
:
switch
(
pc
[
1
])
{
case
0xfb
:
/* sti */
case
0x9d
:
/* popf */
case
0xcf
:
/* iret */
env
->
eip
+=
2
;
return_to_32bit
(
env
,
TARGET_VM86_STI
);
break
;
default:
goto
vm86_gpf
;
}
break
;
case
0xfb
:
/* sti */
case
0x9d
:
/* popf */
case
0xcf
:
/* iret */
env
->
eip
++
;
return_to_32bit
(
env
,
TARGET_VM86_STI
);
break
;
default:
vm86_gpf:
/* real VM86 GPF exception */
ret
=
TARGET_VM86_UNKNOWN
;
ret
urn_to_32bit
(
env
,
TARGET_VM86_UNKNOWN
)
;
break
;
}
#ifdef DEBUG_VM86
printf
(
"ret=0x%x
\n
"
,
ret
);
#endif
/* put the VM86 registers in the userspace register structure */
ts
->
target_v86
->
regs
.
eax
=
tswap32
(
env
->
regs
[
R_EAX
]);
ts
->
target_v86
->
regs
.
ebx
=
tswap32
(
env
->
regs
[
R_EBX
]);
ts
->
target_v86
->
regs
.
ecx
=
tswap32
(
env
->
regs
[
R_ECX
]);
ts
->
target_v86
->
regs
.
edx
=
tswap32
(
env
->
regs
[
R_EDX
]);
ts
->
target_v86
->
regs
.
esi
=
tswap32
(
env
->
regs
[
R_ESI
]);
ts
->
target_v86
->
regs
.
edi
=
tswap32
(
env
->
regs
[
R_EDI
]);
ts
->
target_v86
->
regs
.
ebp
=
tswap32
(
env
->
regs
[
R_EBP
]);
ts
->
target_v86
->
regs
.
esp
=
tswap32
(
env
->
regs
[
R_ESP
]);
ts
->
target_v86
->
regs
.
eip
=
tswap32
(
env
->
eip
);
ts
->
target_v86
->
regs
.
cs
=
tswap16
(
env
->
segs
[
R_CS
]);
ts
->
target_v86
->
regs
.
ss
=
tswap16
(
env
->
segs
[
R_SS
]);
ts
->
target_v86
->
regs
.
ds
=
tswap16
(
env
->
segs
[
R_DS
]);
ts
->
target_v86
->
regs
.
es
=
tswap16
(
env
->
segs
[
R_ES
]);
ts
->
target_v86
->
regs
.
fs
=
tswap16
(
env
->
segs
[
R_FS
]);
ts
->
target_v86
->
regs
.
gs
=
tswap16
(
env
->
segs
[
R_GS
]);
/* restore 32 bit registers */
env
->
regs
[
R_EBX
]
=
ts
->
vm86_saved_regs
.
ebx
;
env
->
regs
[
R_ECX
]
=
ts
->
vm86_saved_regs
.
ecx
;
env
->
regs
[
R_EDX
]
=
ts
->
vm86_saved_regs
.
edx
;
env
->
regs
[
R_ESI
]
=
ts
->
vm86_saved_regs
.
esi
;
env
->
regs
[
R_EDI
]
=
ts
->
vm86_saved_regs
.
edi
;
env
->
regs
[
R_EBP
]
=
ts
->
vm86_saved_regs
.
ebp
;
env
->
regs
[
R_ESP
]
=
ts
->
vm86_saved_regs
.
esp
;
env
->
eflags
=
ts
->
vm86_saved_regs
.
eflags
;
env
->
eip
=
ts
->
vm86_saved_regs
.
eip
;
cpu_x86_load_seg
(
env
,
R_CS
,
ts
->
vm86_saved_regs
.
cs
);
cpu_x86_load_seg
(
env
,
R_SS
,
ts
->
vm86_saved_regs
.
ss
);
cpu_x86_load_seg
(
env
,
R_DS
,
ts
->
vm86_saved_regs
.
ds
);
cpu_x86_load_seg
(
env
,
R_ES
,
ts
->
vm86_saved_regs
.
es
);
cpu_x86_load_seg
(
env
,
R_FS
,
ts
->
vm86_saved_regs
.
fs
);
cpu_x86_load_seg
(
env
,
R_GS
,
ts
->
vm86_saved_regs
.
gs
);
env
->
regs
[
R_EAX
]
=
ret
;
}
else
{
if
(
pc
[
0
]
==
0xcd
&&
pc
[
1
]
==
0x80
)
{
/* syscall */
...
...
@@ -200,20 +295,28 @@ void cpu_loop(struct CPUX86State *env)
}
break
;
case
EXCP00_DIVZ
:
/* division by zero */
info
.
si_signo
=
SIGFPE
;
info
.
si_errno
=
0
;
info
.
si_code
=
TARGET_FPE_INTDIV
;
info
.
_sifields
.
_sigfault
.
_addr
=
env
->
eip
;
queue_signal
(
info
.
si_signo
,
&
info
);
if
(
env
->
eflags
&
VM_MASK
)
{
do_int
(
env
,
trapnr
);
}
else
{
/* division by zero */
info
.
si_signo
=
SIGFPE
;
info
.
si_errno
=
0
;
info
.
si_code
=
TARGET_FPE_INTDIV
;
info
.
_sifields
.
_sigfault
.
_addr
=
env
->
eip
;
queue_signal
(
info
.
si_signo
,
&
info
);
}
break
;
case
EXCP04_INTO
:
case
EXCP05_BOUND
:
info
.
si_signo
=
SIGSEGV
;
info
.
si_errno
=
0
;
info
.
si_code
=
0
;
info
.
_sifields
.
_sigfault
.
_addr
=
0
;
queue_signal
(
info
.
si_signo
,
&
info
);
if
(
env
->
eflags
&
VM_MASK
)
{
do_int
(
env
,
trapnr
);
}
else
{
info
.
si_signo
=
SIGSEGV
;
info
.
si_errno
=
0
;
info
.
si_code
=
0
;
info
.
_sifields
.
_sigfault
.
_addr
=
0
;
queue_signal
(
info
.
si_signo
,
&
info
);
}
break
;
case
EXCP06_ILLOP
:
info
.
si_signo
=
SIGILL
;
...
...
@@ -226,8 +329,8 @@ void cpu_loop(struct CPUX86State *env)
/* just indicate that signals should be handled asap */
break
;
default:
fprintf
(
stderr
,
"
0x%08lx: Unknown exception CPU %d,
aborting
\n
"
,
(
long
)
pc
,
er
r
);
fprintf
(
stderr
,
"
qemu: 0x%08lx: unhandled CPU exception 0x%x -
aborting
\n
"
,
(
long
)
pc
,
trapn
r
);
abort
();
}
process_pending_signals
(
env
);
...
...
linux-user/qemu.h
浏览文件 @
bc8a22cc
...
...
@@ -74,5 +74,6 @@ void cpu_loop(CPUX86State *env);
void
process_pending_signals
(
void
*
cpu_env
);
void
signal_init
(
void
);
int
queue_signal
(
int
sig
,
target_siginfo_t
*
info
);
void
save_v86_state
(
CPUX86State
*
env
);
#endif
linux-user/signal.c
浏览文件 @
bc8a22cc
...
...
@@ -198,7 +198,7 @@ void __attribute((noreturn)) force_sig(int sig)
{
int
host_sig
;
host_sig
=
target_to_host_signal
(
sig
);
fprintf
(
stderr
,
"
g
emu: uncaught target signal %d (%s) - exiting
\n
"
,
fprintf
(
stderr
,
"
q
emu: uncaught target signal %d (%s) - exiting
\n
"
,
sig
,
strsignal
(
host_sig
));
#if 1
_exit
(
-
host_sig
);
...
...
@@ -223,7 +223,7 @@ int queue_signal(int sig, target_siginfo_t *info)
target_ulong
handler
;
#if defined(DEBUG_SIGNAL)
fprintf
(
stderr
,
"queue_sigal: sig=%d
\n
"
,
fprintf
(
stderr
,
"queue_sig
n
al: sig=%d
\n
"
,
sig
);
#endif
k
=
&
sigact_table
[
sig
-
1
];
...
...
@@ -317,7 +317,7 @@ static void host_signal_handler(int host_signum, siginfo_t *info,
if
(
sig
<
1
||
sig
>
TARGET_NSIG
)
return
;
#if defined(DEBUG_SIGNAL)
fprintf
(
stderr
,
"
g
emu: got signal %d
\n
"
,
sig
);
fprintf
(
stderr
,
"
q
emu: got signal %d
\n
"
,
sig
);
dump_regs
(
puc
);
#endif
host_to_target_siginfo_noswap
(
&
tinfo
,
info
);
...
...
@@ -538,7 +538,6 @@ setup_sigcontext(struct target_sigcontext *sc, struct target_fpstate *fpstate,
/* non-iBCS2 extensions.. */
err
|=
__put_user
(
mask
,
&
sc
->
oldmask
);
err
|=
__put_user
(
/*current->thread.cr2*/
0
,
&
sc
->
cr2
);
return
err
;
}
...
...
@@ -859,7 +858,7 @@ void process_pending_signals(void *cpu_env)
handle_signal:
#ifdef DEBUG_SIGNAL
fprintf
(
stderr
,
"
g
emu: process signal %d
\n
"
,
sig
);
fprintf
(
stderr
,
"
q
emu: process signal %d
\n
"
,
sig
);
#endif
/* dequeue signal */
q
=
k
->
first
;
...
...
@@ -893,6 +892,14 @@ void process_pending_signals(void *cpu_env)
end of the signal execution (see do_sigreturn) */
host_to_target_sigset
(
&
target_old_set
,
&
old_set
);
/* if the CPU is in VM86 mode, we restore the 32 bit values */
#ifdef TARGET_I386
{
CPUX86State
*
env
=
cpu_env
;
if
(
env
->
eflags
&
VM_MASK
)
save_v86_state
(
env
);
}
#endif
/* prepare the stack frame of the virtual CPU */
if
(
k
->
sa
.
sa_flags
&
TARGET_SA_SIGINFO
)
setup_rt_frame
(
sig
,
k
,
&
q
->
info
,
&
target_old_set
,
cpu_env
);
...
...
syscall-i386.h
浏览文件 @
bc8a22cc
...
...
@@ -755,6 +755,11 @@ struct target_modify_ldt_ldt_s {
unsigned
int
flags
;
};
/* vm86 defines */
#define TARGET_BIOSSEG 0x0f000
#define TARGET_VM86_SIGNAL 0
/* return due to signal */
#define TARGET_VM86_UNKNOWN 1
/* unhandled GP fault - IO-instruction or similar */
#define TARGET_VM86_INTx 2
/* int3/int x instruction (ARG = x) */
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录