Commit b4e1e5e5 authored by unknown's avatar unknown

Merge vvaintroub@bk-internal.mysql.com:/home/bk/mysql-5.1-build

into  wva.:C:/bk/bug31745_2/mysql-5.1-build


sql/mysqld.cc:
  Auto merged
sql/sql_parse.cc:
  Auto merged
sql/stacktrace.h:
  Auto merged
sql/stacktrace.c:
  SCCS merged
parents 3b2aea38 8b91756b
......@@ -43,7 +43,7 @@ ADD_DEFINITIONS(-DMYSQL_SERVER -D_CONSOLE -DHAVE_DLOPEN)
ADD_EXECUTABLE(mysqld
../sql-common/client.c derror.cc des_key_file.cc
discover.cc ../libmysql/errmsg.c field.cc field_conv.cc
discover.cc ../libmysql/errmsg.c field.cc stacktrace.c stacktrace.h field_conv.cc
filesort.cc gstream.cc
ha_partition.cc
handler.cc hash_filo.cc hash_filo.h
......
......@@ -121,6 +121,13 @@ extern "C" { // Because of SCO 3.2V4.2
#include <sys/mman.h>
#endif
#ifdef __WIN__
#include <crtdbg.h>
#define SIGNAL_FMT "exception 0x%x"
#else
#define SIGNAL_FMT "signal %d"
#endif
#ifdef __NETWARE__
#define zVOLSTATE_ACTIVE 6
#define zVOLSTATE_DEACTIVE 2
......@@ -217,6 +224,7 @@ inline void set_proper_floating_point_mode()
extern "C" int gethostname(char *name, int namelen);
#endif
extern "C" sig_handler handle_segfault(int sig);
/* Constants */
......@@ -1085,9 +1093,6 @@ static void __cdecl kill_server(int sig_ptr)
close_connections();
if (sig != MYSQL_KILL_SIGNAL &&
#ifdef __WIN__
sig != SIGINT && /* Bug#18235 */
#endif
sig != 0)
unireg_abort(1); /* purecov: inspected */
else
......@@ -1656,8 +1661,7 @@ static void network_init(void)
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL );
MessageBox(NULL, (LPTSTR) lpMsgBuf, "Error from CreateNamedPipe",
MB_OK|MB_ICONINFORMATION);
sql_perror((char *)lpMsgBuf);
LocalFree(lpMsgBuf);
unireg_abort(1);
}
......@@ -1916,16 +1920,162 @@ extern "C" sig_handler abort_thread(int sig __attribute__((unused)))
******************************************************************************/
#if defined(__WIN__)
/*
On Windows, we use native SetConsoleCtrlHandler for handle events like Ctrl-C
with graceful shutdown.
Also, we do not use signal(), but SetUnhandledExceptionFilter instead - as it
provides possibility to pass the exception to just-in-time debugger, collect
dumps and potentially also the exception and thread context used to output
callstack.
*/
static BOOL WINAPI console_event_handler( DWORD type )
{
DBUG_ENTER("console_event_handler");
if(type == CTRL_C_EVENT)
{
/*
Do not shutdown before startup is finished and shutdown
thread is initialized. Otherwise there is a race condition
between main thread doing initialization and CTRL-C thread doing
cleanup, which can result into crash.
*/
if(hEventShutdown)
kill_mysql();
else
sql_print_warning("CTRL-C ignored during startup");
DBUG_RETURN(TRUE);
}
DBUG_RETURN(FALSE);
}
/*
In Visual Studio 2005 and later, default SIGABRT handler will overwrite
any unhandled exception filter set by the application and will try to
call JIT debugger. This is not what we want, this we calling __debugbreak
to stop in debugger, if process is being debugged or to generate
EXCEPTION_BREAKPOINT and then handle_segfault will do its magic.
*/
#if (_MSC_VER >= 1400)
static void my_sigabrt_handler(int sig)
{
__debugbreak();
}
#endif /*_MSC_VER >=1400 */
void win_install_sigabrt_handler(void)
{
#if (_MSC_VER >=1400)
/*abort() should not override our exception filter*/
_set_abort_behavior(0,_CALL_REPORTFAULT);
signal(SIGABRT,my_sigabrt_handler);
#endif /* _MSC_VER >=1400 */
}
#ifdef DEBUG_UNHANDLED_EXCEPTION_FILTER
#define DEBUGGER_ATTACH_TIMEOUT 120
/*
Wait for debugger to attach and break into debugger. If debugger is not attached,
resume after timeout.
*/
static void wait_for_debugger(int timeout_sec)
{
if(!IsDebuggerPresent())
{
int i;
printf("Waiting for debugger to attach, pid=%u\n",GetCurrentProcessId());
fflush(stdout);
for(i= 0; i < timeout_sec; i++)
{
Sleep(1000);
if(IsDebuggerPresent())
{
/* Break into debugger */
__debugbreak();
return;
}
}
printf("pid=%u, debugger not attached after %d seconds, resuming\n",GetCurrentProcessId(),
timeout_sec);
fflush(stdout);
}
}
#endif /* DEBUG_UNHANDLED_EXCEPTION_FILTER */
LONG WINAPI my_unhandler_exception_filter(EXCEPTION_POINTERS *ex_pointers)
{
static BOOL first_time= TRUE;
if(!first_time)
{
/*
This routine can be called twice, typically
when detaching in JIT debugger.
Return EXCEPTION_EXECUTE_HANDLER to terminate process.
*/
return EXCEPTION_EXECUTE_HANDLER;
}
first_time= FALSE;
#ifdef DEBUG_UNHANDLED_EXCEPTION_FILTER
/*
Unfortunately there is no clean way to debug unhandled exception filters,
as debugger does not stop there(also documented in MSDN)
To overcome, one could put a MessageBox, but this will not work in service.
Better solution is to print error message and sleep some minutes
until debugger is attached
*/
wait_for_debugger(DEBUGGER_ATTACH_TIMEOUT);
#endif /* DEBUG_UNHANDLED_EXCEPTION_FILTER */
__try
{
set_exception_pointers(ex_pointers);
handle_segfault(ex_pointers->ExceptionRecord->ExceptionCode);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
DWORD written;
const char msg[] = "Got exception in exception handler!\n";
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),msg, sizeof(msg)-1,
&written,NULL);
}
/*
Return EXCEPTION_CONTINUE_SEARCH to give JIT debugger
(drwtsn32 or vsjitdebugger) possibility to attach,
if JIT debugger is configured.
Windows Error reporting might generate a dump here.
*/
return EXCEPTION_CONTINUE_SEARCH;
}
static void init_signals(void)
{
int signals[] = {SIGINT,SIGILL,SIGFPE,SIGSEGV,SIGTERM,SIGABRT } ;
for (uint i=0 ; i < sizeof(signals)/sizeof(int) ; i++)
signal(signals[i], kill_server) ;
#if defined(__WIN__)
signal(SIGBREAK,SIG_IGN); //ignore SIGBREAK for NT
#else
signal(SIGBREAK, kill_server);
#endif
win_install_sigabrt_handler();
if(opt_console)
SetConsoleCtrlHandler(console_event_handler,TRUE);
else
{
/* Avoid MessageBox()es*/
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
/*
Do not use SEM_NOGPFAULTERRORBOX in the following SetErrorMode (),
because it would prevent JIT debugger and Windows error reporting
from working. We need WER or JIT-debugging, since our own unhandled
exception filter is not guaranteed to work in all situation
(like heap corruption or stack overflow)
*/
SetErrorMode(SetErrorMode(0)|SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
}
SetUnhandledExceptionFilter(my_unhandler_exception_filter);
}
......@@ -2178,7 +2328,7 @@ static void check_data_home(const char *path)
{
}
#else /* if ! __WIN__ */
#endif /*__WIN__ || __NETWARE */
#ifdef HAVE_LINUXTHREADS
#define UNSAFE_DEFAULT_LINUX_THREADS 200
......@@ -2208,7 +2358,7 @@ extern "C" sig_handler handle_segfault(int sig)
*/
if (segfaulted)
{
fprintf(stderr, "Fatal signal %d while backtracing\n", sig);
fprintf(stderr, "Fatal " SIGNAL_FMT " while backtracing\n", sig);
exit(1);
}
......@@ -2218,7 +2368,7 @@ extern "C" sig_handler handle_segfault(int sig)
localtime_r(&curr_time, &tm);
fprintf(stderr,"\
%02d%02d%02d %2d:%02d:%02d - mysqld got signal %d;\n\
%02d%02d%02d %2d:%02d:%02d - mysqld got " SIGNAL_FMT " ;\n\
This could be because you hit a bug. It is also possible that this binary\n\
or one of the libraries it was linked against is corrupt, improperly built,\n\
or misconfigured. This error can also be caused by malfunctioning hardware.\n",
......@@ -2260,8 +2410,12 @@ the thread stack. Please read http://dev.mysql.com/doc/mysql/en/linux.html\n\n",
if (!(test_flags & TEST_NO_STACKTRACE))
{
fprintf(stderr,"thd: 0x%lx\n",(long) thd);
fprintf(stderr,"\
Attempting backtrace. You can use the following information to find out\n\
where mysqld died. If you see no messages after this, something went\n\
terribly wrong...\n");
print_stacktrace(thd ? (uchar*) thd->thread_stack : (uchar*) 0,
thread_stack);
thread_stack);
}
if (thd)
{
......@@ -2327,15 +2481,22 @@ of those buggy OS calls. You should consider whether you really need the\n\
bugs.\n");
}
#ifdef HAVE_WRITE_CORE
if (test_flags & TEST_CORE_ON_SIGNAL)
{
fprintf(stderr, "Writing a core file\n");
fflush(stderr);
write_core(sig);
}
#endif
#ifndef __WIN__
/* On Windows, do not terminate, but pass control to exception filter */
exit(1);
#endif
}
#if !defined(__WIN__) && !defined(__NETWARE__)
#ifndef SA_RESETHAND
#define SA_RESETHAND 0
#endif
......@@ -2716,18 +2877,6 @@ pthread_handler_t handle_shutdown(void *arg)
kill_server(MYSQL_KILL_SIGNAL);
return 0;
}
int STDCALL handle_kill(ulong ctrl_type)
{
if (ctrl_type == CTRL_CLOSE_EVENT ||
ctrl_type == CTRL_SHUTDOWN_EVENT)
{
kill_server(MYSQL_KILL_SIGNAL);
return TRUE;
}
return FALSE;
}
#endif
#if !defined(EMBEDDED_LIBRARY)
......@@ -4091,11 +4240,6 @@ we force server id to 2, but this MySQL server will not act as a slave.");
freopen(log_error_file,"a+",stderr);
FreeConsole(); // Remove window
}
else
{
/* Don't show error dialog box when on foreground: it stops the server */
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
}
#endif
/*
......
......@@ -39,22 +39,7 @@
#endif /* HAVE_OPENSSL */
#ifdef __WIN__
static void test_signal(int sig_ptr)
{
#if !defined( DBUG_OFF)
MessageBox(NULL,"Test signal","DBUG",MB_OK);
#endif
#if defined(OS2)
fprintf(stderr, "Test signal %d\n", sig_ptr);
fflush(stderr);
#endif
}
static void init_signals(void)
{
int signals[7] = {SIGINT,SIGILL,SIGFPE,SIGSEGV,SIGTERM,SIGBREAK,SIGABRT } ;
for (int i=0 ; i < 7 ; i++)
signal( signals[i], test_signal) ;
}
extern void win_install_sigabrt_handler();
#endif
/*
......@@ -626,7 +611,7 @@ bool init_new_connection_handler_thread()
{
pthread_detach_this_thread();
#if defined(__WIN__)
init_signals();
win_install_sigabrt_handler();
#else
/* Win32 calls this in pthread_create */
if (my_thread_init())
......
......@@ -13,12 +13,16 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* Workaround for Bug#32082: VOID redefinition on Win results in compile errors*/
#define DONT_DEFINE_VOID 1
#include <my_global.h>
#include "stacktrace.h"
#ifndef __WIN__
#include <signal.h>
#include <my_pthread.h>
#include <m_string.h>
#ifdef HAVE_STACKTRACE
#include <unistd.h>
#include <strings.h>
......@@ -167,10 +171,7 @@ void print_stacktrace(uchar* stack_bottom, ulong thread_stack)
#endif
LINT_INIT(fp);
fprintf(stderr,"\
Attempting backtrace. You can use the following information to find out\n\
where mysqld died. If you see no messages after this, something went\n\
terribly wrong...\n");
#ifdef __i386__
__asm __volatile__ ("movl %%ebp,%0"
:"=r"(fp)
......@@ -308,3 +309,267 @@ void write_core(int sig)
#endif
}
#endif
#else /* __WIN__*/
#include <dbghelp.h>
/*
Stack tracing on Windows is implemented using Debug Helper library(dbghelp.dll)
We do not redistribute dbghelp and the one comes with older OS (up to Windows 2000)
is missing some important functions like functions StackWalk64 or MinidumpWriteDump.
Hence, we have to load functions at runtime using LoadLibrary/GetProcAddress.
*/
typedef DWORD (WINAPI *SymSetOptions_FctType)(DWORD dwOptions);
typedef BOOL (WINAPI *SymGetModuleInfo64_FctType)
(HANDLE,DWORD64,PIMAGEHLP_MODULE64) ;
typedef BOOL (WINAPI *SymGetSymFromAddr64_FctType)
(HANDLE,DWORD64,PDWORD64,PIMAGEHLP_SYMBOL64) ;
typedef BOOL (WINAPI *SymGetLineFromAddr64_FctType)
(HANDLE,DWORD64,PDWORD,PIMAGEHLP_LINE64);
typedef BOOL (WINAPI *SymInitialize_FctType)
(HANDLE,PSTR,BOOL);
typedef BOOL (WINAPI *StackWalk64_FctType)
(DWORD,HANDLE,HANDLE,LPSTACKFRAME64,PVOID,PREAD_PROCESS_MEMORY_ROUTINE64,
PFUNCTION_TABLE_ACCESS_ROUTINE64,PGET_MODULE_BASE_ROUTINE64 ,
PTRANSLATE_ADDRESS_ROUTINE64);
typedef BOOL (WINAPI *MiniDumpWriteDump_FctType)(
IN HANDLE hProcess,
IN DWORD ProcessId,
IN HANDLE hFile,
IN MINIDUMP_TYPE DumpType,
IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL
IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL
IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL
);
static SymSetOptions_FctType pSymSetOptions;
static SymGetModuleInfo64_FctType pSymGetModuleInfo64;
static SymGetSymFromAddr64_FctType pSymGetSymFromAddr64;
static SymInitialize_FctType pSymInitialize;
static StackWalk64_FctType pStackWalk64;
static SymGetLineFromAddr64_FctType pSymGetLineFromAddr64;
static MiniDumpWriteDump_FctType pMiniDumpWriteDump;
static EXCEPTION_POINTERS *exception_ptrs;
#define MODULE64_SIZE_WINXP 576
#define STACKWALK_MAX_FRAMES 64
/*
Dynamically load dbghelp functions
*/
BOOL init_dbghelp_functions()
{
static BOOL first_time= TRUE;
static BOOL rc;
HMODULE hDbghlp;
if(first_time)
{
first_time= FALSE;
hDbghlp= LoadLibrary("dbghelp");
if(!hDbghlp)
{
rc= FALSE;
return rc;
}
pSymSetOptions= (SymSetOptions_FctType)
GetProcAddress(hDbghlp,"SymSetOptions");
pSymInitialize= (SymInitialize_FctType)
GetProcAddress(hDbghlp,"SymInitialize");
pSymGetModuleInfo64= (SymGetModuleInfo64_FctType)
GetProcAddress(hDbghlp,"SymGetModuleInfo64");
pSymGetLineFromAddr64= (SymGetLineFromAddr64_FctType)
GetProcAddress(hDbghlp,"SymGetLineFromAddr64");
pSymGetSymFromAddr64=(SymGetSymFromAddr64_FctType)
GetProcAddress(hDbghlp,"SymGetSymFromAddr64");
pStackWalk64= (StackWalk64_FctType)
GetProcAddress(hDbghlp,"StackWalk64");
pMiniDumpWriteDump = (MiniDumpWriteDump_FctType)
GetProcAddress(hDbghlp,"MiniDumpWriteDump");
rc = (BOOL)(pSymSetOptions && pSymInitialize && pSymGetModuleInfo64
&& pSymGetLineFromAddr64 && pSymGetSymFromAddr64 && pStackWalk64);
}
return rc;
}
void set_exception_pointers(EXCEPTION_POINTERS *ep)
{
exception_ptrs = ep;
}
/* Platform SDK in VS2003 does not have definition for SYMOPT_NO_PROMPTS*/
#ifndef SYMOPT_NO_PROMPTS
#define SYMOPT_NO_PROMPTS 0
#endif
void print_stacktrace(uchar* unused1, ulong unused2)
{
HANDLE hProcess= GetCurrentProcess();
HANDLE hThread= GetCurrentThread();
static IMAGEHLP_MODULE64 module= {sizeof(module)};
static IMAGEHLP_SYMBOL64_PACKAGE package;
DWORD64 addr;
DWORD machine;
int i;
CONTEXT context;
STACKFRAME64 frame={0};
if(!exception_ptrs || !init_dbghelp_functions())
return;
/* Copy context, as stackwalking on original will unwind the stack */
context = *(exception_ptrs->ContextRecord);
/*Initialize symbols.*/
pSymSetOptions(SYMOPT_LOAD_LINES|SYMOPT_NO_PROMPTS|SYMOPT_DEFERRED_LOADS|SYMOPT_DEBUG);
pSymInitialize(hProcess,NULL,TRUE);
/*Prepare stackframe for the first StackWalk64 call*/
frame.AddrFrame.Mode= frame.AddrPC.Mode= frame.AddrStack.Mode= AddrModeFlat;
#if (defined _M_IX86)
machine= IMAGE_FILE_MACHINE_I386;
frame.AddrFrame.Offset= context.Ebp;
frame.AddrPC.Offset= context.Eip;
frame.AddrStack.Offset= context.Esp;
#elif (defined _M_X64)
machine = IMAGE_FILE_MACHINE_AMD64;
frame.AddrFrame.Offset= context.Rbp;
frame.AddrPC.Offset= context.Rip;
frame.AddrStack.Offset= context.Rsp;
#else
/*There is currently no need to support IA64*/
#pragma error ("unsupported architecture")
#endif
package.sym.SizeOfStruct= sizeof(package.sym);
package.sym.MaxNameLength= sizeof(package.name);
/*Walk the stack, output useful information*/
for(i= 0; i< STACKWALK_MAX_FRAMES;i++)
{
DWORD64 function_offset= 0;
DWORD line_offset= 0;
IMAGEHLP_LINE64 line= {sizeof(line)};
BOOL have_module= FALSE;
BOOL have_symbol= FALSE;
BOOL have_source= FALSE;
if(!pStackWalk64(machine, hProcess, hThread, &frame, &context, 0, 0, 0 ,0))
break;
addr= frame.AddrPC.Offset;
have_module= pSymGetModuleInfo64(hProcess,addr,&module);
#ifdef _M_IX86
if(!have_module)
{
/*
ModuleInfo structure has been "compatibly" extended in releases after XP,
and its size was increased. To make XP dbghelp.dll function
happy, pretend passing the old structure.
*/
module.SizeOfStruct= MODULE64_SIZE_WINXP;
have_module= pSymGetModuleInfo64(hProcess, addr, &module);
}
#endif
have_symbol= pSymGetSymFromAddr64(hProcess, addr, &function_offset,
&(package.sym));
have_source= pSymGetLineFromAddr64(hProcess, addr, &line_offset, &line);
fprintf(stderr, "%p ", addr);
if(have_module)
{
char *base_image_name= strrchr(module.ImageName, '\\');
if(base_image_name)
base_image_name++;
else
base_image_name= module.ImageName;
fprintf(stderr, "%s!", base_image_name);
}
if(have_symbol)
fprintf(stderr, "%s()", package.sym.Name);
else if(have_module)
fprintf(stderr, "???");
if(have_source)
{
char *base_file_name= strrchr(line.FileName, '\\');
if(base_file_name)
base_file_name++;
else
base_file_name= line.FileName;
fprintf(stderr,"[%s:%u]", base_file_name, line.LineNumber);
}
fprintf(stderr, "\n");
}
fflush(stderr);
}
/*
Write dump. The dump is created in current directory,
file name is constructed from executable name plus
".dmp" extension
*/
void write_core(int unused)
{
char path[MAX_PATH];
char dump_fname[MAX_PATH]= "core.dmp";
MINIDUMP_EXCEPTION_INFORMATION info;
HANDLE hFile;
if(!exception_ptrs || !init_dbghelp_functions() || !pMiniDumpWriteDump)
return;
info.ExceptionPointers= exception_ptrs;
info.ClientPointers= FALSE;
info.ThreadId= GetCurrentThreadId();
if(GetModuleFileName(NULL, path, sizeof(path)))
{
_splitpath(path, NULL, NULL,dump_fname,NULL);
strncat(dump_fname, ".dmp", sizeof(dump_fname));
}
hFile= CreateFile(dump_fname, GENERIC_WRITE, 0, 0, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
if(hFile)
{
/* Create minidump */
if(pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
hFile, MiniDumpNormal, &info, 0, 0))
{
fprintf(stderr, "Minidump written to %s\n",
_fullpath(path, dump_fname, sizeof(path)) ? path : dump_fname);
}
else
{
fprintf(stderr,"MiniDumpWriteDump() failed, last error %u\n",
GetLastError());
}
CloseHandle(hFile);
}
else
{
fprintf(stderr, "CreateFile(%s) failed, last error %u\n", dump_fname,
GetLastError());
}
fflush(stderr);
}
void safe_print_str(const char *name, const char *val, int len)
{
fprintf(stderr,"%s at %p", name, val);
__try
{
fprintf(stderr,"=%.*s\n", len, val);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
fprintf(stderr,"is an invalid string pointer\n");
}
}
#endif /*__WIN__*/
......@@ -36,19 +36,33 @@ extern char* heap_start;
#define init_stacktrace() do { \
heap_start = (char*) &__bss_start; \
} while(0);
void check_thread_lib(void);
#endif /* defined (__i386__) || (defined(__alpha__) && defined(__GNUC__))) */
#elif defined (__WIN__)
#define HAVE_STACKTRACE
extern void set_exception_pointers(EXCEPTION_POINTERS *ep);
#define init_stacktrace() {}
#endif
#ifdef HAVE_STACKTRACE
void print_stacktrace(uchar* stack_bottom, ulong thread_stack);
void safe_print_str(const char* name, const char* val, int max_len);
#endif /* (defined (__i386__) || (defined(__alpha__) && defined(__GNUC__))) */
#endif /* TARGET_OS_LINUX */
#else
/* Define empty prototypes for functions that are not implemented */
#ifndef HAVE_STACKTRACE
#define init_stacktrace() {}
#define print_stacktrace(A,B) {}
#define safe_print_str(A,B,C) {}
#endif /* HAVE_STACKTRACE */
#if !defined(__NETWARE__)
#define HAVE_WRITE_CORE
#endif
#ifdef HAVE_WRITE_CORE
void write_core(int sig);
#endif
#ifdef __cplusplus
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment