diff --git a/Quake/common.c b/Quake/common.c index 579e624df..7606b1703 100644 --- a/Quake/common.c +++ b/Quake/common.c @@ -774,7 +774,7 @@ void MSG_WriteChar (sizebuf_t *sb, int c) { byte *buf; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (c < -128 || c > 127) Host_Error ("MSG_WriteChar: range error = %i not in -128..127", c); #endif @@ -787,7 +787,7 @@ void MSG_WriteByte (sizebuf_t *sb, int c) { byte *buf; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (c < 0 || c > 255) Host_Error ("MSG_WriteByte: range error = %i not in 0..255", c); #endif @@ -800,7 +800,7 @@ void MSG_WriteShort (sizebuf_t *sb, int c) { byte *buf; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // it is apparently used to encode signed OR unsigned shorts... if (c < INT16_MIN || c > UINT16_MAX) Host_Error ("MSG_WriteShort: range error = %i not in -32768..65535", c); @@ -3435,3 +3435,32 @@ int32_t COM_Rand () return (int32_t)(result & COM_RAND_MAX); } + +void COM_Assert_Failed (const char *expr, const char *file_path, int line) +{ + // only keep the simple file name, strip the directory part + // we only want the short file name, not the full path: + char *last_sep = strrchr (file_path, '\\'); + + if (!last_sep) + last_sep = strrchr (file_path, '/'); + + const char *filename = (last_sep ? last_sep + 1 : file_path); + + if (Tasks_IsWorker ()) + { + Sys_DebugBreak (); + + if (!Sys_IsInDebugger ()) + { + char msg[4096]; + q_snprintf (msg, 4096, "%s:%d Assertion: '%s' failed\n", filename, line, expr); + q_snprintf (msg + strnlen (msg, 4096), 4096, "STACK TRACE:\n"); + q_snprintf (msg + strnlen (msg, 4096), 4096, "%s", Sys_StackTrace ()); + PL_ErrorDialog (msg); + } + exit (1); + } + else // We are in the main thread, console is accessible, do Host_Error and we can recover. + Host_Error ("%s:%d Assertion: '%s' failed", filename, line, expr); +} diff --git a/Quake/common.h b/Quake/common.h index c17f3926b..b00116985 100644 --- a/Quake/common.h +++ b/Quake/common.h @@ -333,6 +333,9 @@ int32_t COM_Rand (void); // Limit to 24 bits so values fit in float mantissa & don't get negative when casting to ints #define COM_RAND_MAX 0xFFFFFF +// Utility for assert() redefinition +void COM_Assert_Failed (const char *expr, const char *file_path, int line); + //============================================================================ // QUAKEFS diff --git a/Quake/console.c b/Quake/console.c index 5fd82c175..398366d55 100644 --- a/Quake/console.c +++ b/Quake/console.c @@ -1275,8 +1275,8 @@ void LOG_Init (quakeparms_t *parms) time_t inittime; char session[24]; - // always activate the console log in PARANOID mode -#if !defined(PARANOID) + // always activate the console log in Debug mode +#if !defined(DEBUG) && !defined(_DEBUG) if (!COM_CheckParm ("-condebug")) return; #endif diff --git a/Quake/host.c b/Quake/host.c index 44deecec7..e7d5fb17a 100644 --- a/Quake/host.c +++ b/Quake/host.c @@ -219,17 +219,23 @@ void Host_Error (const char *error, ...) Sys_Error ("Host_Error: recursively entered"); inerror = true; - Con_Printf ("================ STACK TRACE ================\n"); - Con_Printf ("%s", Sys_StackTrace ()); - Con_Printf ("=============================================\n"); + va_start (argptr, error); + q_vsnprintf (string, sizeof (string), error, argptr); + va_end (argptr); + + Sys_DebugBreak (); + + if (!Sys_IsInDebugger ()) + { + Con_Printf ("================ STACK TRACE ================\n"); + Con_Printf ("%s", Sys_StackTrace ()); + Con_Printf ("=============================================\n"); + } PR_SwitchQCVM (NULL); SCR_EndLoadingPlaque (); // reenable screen updates - va_start (argptr, error); - q_vsnprintf (string, sizeof (string), error, argptr); - va_end (argptr); Con_Printf ("Host_Error: %s\n", string); if (cl.qcvm.extfuncs.CSQC_DrawHud && in_update_screen) @@ -308,8 +314,8 @@ void Host_Version_f (void) Con_Printf ("QuakeSpasm Version " QUAKESPASM_VER_STRING "\n"); Con_Printf ("vkQuake Version " ENGINE_NAME_AND_VER "\n"); -#ifdef PARANOID - const char *build_str_suffix = "(PARANOID build)"; +#if defined(DEBUG) || defined(_DEBUG) + const char *build_str_suffix = "(DEBUG build)"; #else const char *build_str_suffix = ""; #endif @@ -815,7 +821,7 @@ static void CL_LoadCSProgs (void) qcvm->max_edicts = CLAMP (MIN_EDICTS, (int)max_edicts.value, MAX_EDICTS); qcvm->edicts = (edict_t *)Mem_Alloc (qcvm->max_edicts * qcvm->edict_size); qcvm->num_edicts = qcvm->reserved_edicts = 1; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // set debug fiels for all max_edicts for (int i = 0; i < qcvm->max_edicts; i++) { @@ -1102,8 +1108,8 @@ void Host_Init (void) NET_Init (); SV_Init (); -#ifdef PARANOID - const char *build_str_suffix = "(PARANOID build)"; +#if defined(DEBUG) || defined(_DEBUG) + const char *build_str_suffix = "(DEBUG build)"; #else const char *build_str_suffix = ""; #endif diff --git a/Quake/host_cmd.c b/Quake/host_cmd.c index 94c21fad6..159a6add6 100644 --- a/Quake/host_cmd.c +++ b/Quake/host_cmd.c @@ -1607,7 +1607,7 @@ static void Host_Loadgame_f (void) assert (!ent->free); ent->baseline = nullentitystate; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // fill debug fields, they were overwriten above: ent->qcvm_owner = qcvm; ent->edict_ptr = ent; diff --git a/Quake/mathlib.c b/Quake/mathlib.c index 068e7f787..b59f50419 100644 --- a/Quake/mathlib.c +++ b/Quake/mathlib.c @@ -189,7 +189,7 @@ int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) dist1 = p->normal[0] * (xneg ? emins : emaxs)[0] + p->normal[1] * (yneg ? emins : emaxs)[1] + p->normal[2] * (zneg ? emins : emaxs)[2]; dist2 = p->normal[0] * (xneg ? emaxs : emins)[0] + p->normal[1] * (yneg ? emaxs : emins)[1] + p->normal[2] * (zneg ? emaxs : emins)[2]; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (p->signbits & ~7) Sys_Error ("BoxOnPlaneSide: Bad signbits"); #endif @@ -226,7 +226,7 @@ int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) if (dist2 < p->dist) sides |= 2; -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (sides == 0) Sys_Error ("BoxOnPlaneSide: sides==0"); #endif @@ -445,14 +445,12 @@ void FloorDivMod (double numer, double denom, int *quotient, int *rem) int q, r; double x; -#ifndef PARANOID if (denom <= 0.0) Sys_Error ("FloorDivMod: bad denominator %f\n", denom); -// if ((floor(numer) != numer) || (floor(denom) != denom)) -// Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n", -// numer, denom); -#endif + // if ((floor(numer) != numer) || (floor(denom) != denom)) + // Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n", + // numer, denom); if (numer >= 0.0) { diff --git a/Quake/mem.c b/Quake/mem.c index 2d6376418..18dab256d 100644 --- a/Quake/mem.c +++ b/Quake/mem.c @@ -43,7 +43,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef vsnprintf #define vsnprintf q_vsnprintf -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // activate mimalloc debug and guarded pages: #define MI_DEBUG 3 #define MI_GUARDED 1 diff --git a/Quake/pr_edict.c b/Quake/pr_edict.c index 85fe15d6f..191378b40 100644 --- a/Quake/pr_edict.c +++ b/Quake/pr_edict.c @@ -92,7 +92,7 @@ edict_t *ED_Alloc (void) assert (!e->free); -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // fill debug fields, they were overwriten above: e->qcvm_owner = qcvm; e->edict_ptr = e; @@ -109,7 +109,7 @@ ED_AddToFreeList */ static void ED_AddToFreeList (edict_t *ed) { -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (qcvm->free_list.size >= MAX_EDICTS) Host_Error ("ED_AddToFreeList : is full (qcvm 0x%p)", qcvm); if (qcvm->free_list.size >= qcvm->max_edicts) @@ -1352,7 +1352,7 @@ qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s, qboolean zoned) // proceed to the same init as new edicts in ED_Alloc: wipe all out, then deallocate it // right away memset (new_edict, 0, qcvm->edict_size); -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // fill debug fields, they were overwriten above: new_edict->qcvm_owner = qcvm; new_edict->edict_ptr = new_edict; @@ -2067,7 +2067,7 @@ edict_t *EDICT_NUM (int n) edict_t *found_edict = EDICT_NUM_NO_CHECK (n); -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (found_edict->edict_num != n) Host_Error ("EDICT_NUM(%i): inconsistent number vs. edict_num=%i", n, (int)found_edict->edict_num); @@ -2079,7 +2079,7 @@ edict_t *EDICT_NUM (int n) int NUM_FOR_EDICT (edict_t *e) { -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (e->qcvm_owner != qcvm) Host_Error ("NUM_FOR_EDICT inconsistent qcvm 0x%p, expected 0x%p", qcvm, e->qcvm_owner); @@ -2095,7 +2095,7 @@ int NUM_FOR_EDICT (edict_t *e) if (b < 0 || b >= qcvm->num_edicts) Host_Error ("NUM_FOR_EDICT: bad pointer"); -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) if (e->edict_num != b) Host_Error ("NUM_FOR_EDICT: inconsistent number %i vs. e.edict_num %i", b, (int)e->edict_num); #endif @@ -2103,7 +2103,7 @@ int NUM_FOR_EDICT (edict_t *e) return b; } -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) edict_t *NEXT_EDICT (edict_t *e) { int current_num = NUM_FOR_EDICT (e); @@ -2113,7 +2113,7 @@ edict_t *NEXT_EDICT (edict_t *e) // in for loops but this is normally fine because the returned value is not used. // here test for last element and return a NULL edict_t* if we go beyond the last element, // and we coredump if that element get used. This NULL checks is only for - // PARANOID mode because it has a noticable performance impact on big edict-heavy levels. + // Debug builds because it has a noticable performance impact on big edict-heavy levels. if (current_num == qcvm->num_edicts - 1) return NULL; diff --git a/Quake/progs.h b/Quake/progs.h index c8e30fbbe..65a1dd485 100644 --- a/Quake/progs.h +++ b/Quake/progs.h @@ -48,7 +48,7 @@ typedef struct qcvm_s qcvm_t; typedef struct edict_s { -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) // by construction, an edict do not move after its creation edict_t *edict_ptr; // edicts are allocated and owned by one qcvm: @@ -138,7 +138,7 @@ void ED_LoadFromFile (const char *data); edict_t *EDICT_NUM (int n); int NUM_FOR_EDICT (edict_t *e); -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) edict_t *NEXT_EDICT (edict_t *e); int EDICT_TO_PROG (edict_t *e); edict_t *PROG_TO_EDICT (int n); diff --git a/Quake/q_stdinc.h b/Quake/q_stdinc.h index d2c07a0ba..de5f8231c 100644 --- a/Quake/q_stdinc.h +++ b/Quake/q_stdinc.h @@ -66,7 +66,6 @@ #include #include #include -#include /*==========================================================================*/ diff --git a/Quake/quakedef.h b/Quake/quakedef.h index 04e9676a0..1803188d3 100644 --- a/Quake/quakedef.h +++ b/Quake/quakedef.h @@ -33,14 +33,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define CONFIG_NAME "vkQuake.cfg" #define SCREENSHOT_PREFIX "vkQuake" -// Instrumented build with extra checks -#if !defined(PARANOID) -// TODO: always valid for MSVC, what about others ? -#if defined(_DEBUG) || defined(DEBUG) -#define PARANOID -#endif -#endif - #define GAMENAME "id1" // directory to look in by default #define PSET_SCRIPT // enable the scriptable particle system (poorly ported from FTE) @@ -356,6 +348,16 @@ static inline int FindLastBitNonZero64 (const uint64_t mask) #include "sys.h" #include "common.h" + +// Our custom assert() output stack traces either on the console with Host_Error +// if we are on the main thread, else print on stdout and abort() +// TBC : performance impact ? reserved for Debug builds ? +#ifndef assert +#define assert(e) ((e) ? (void)0 : COM_Assert_Failed (#e, __FILE__, __LINE__)) +#else +#include +#endif + #include "mem.h" #include "bspfile.h" #include "mathlib.h" diff --git a/Quake/r_world.c b/Quake/r_world.c index 16dba050d..5f973adce 100644 --- a/Quake/r_world.c +++ b/Quake/r_world.c @@ -178,7 +178,15 @@ void R_SetupWorldCBXTexRanges (qboolean use_tasks) texture_t *t = cl.worldmodel->textures[i]; if (!t || !t->texturechains[chain_world] || t->texturechains[chain_world]->flags & (SURF_DRAWTURB | SURF_DRAWTILED)) continue; + assert (current_cbx < NUM_WORLD_CBX); + // TODO : quick hack to shutup MSYS2 error: + // error: array subscript 6 is above array bounds of 'int[6]' [-Werror=array-bounds=] + // world_texend[current_cbx] = i + 1; + // ==> potential bug or zealous compiler triggered by assert (current_cbx < NUM_WORLD_CBX); ? + if (current_cbx >= NUM_WORLD_CBX) + break; + world_texend[current_cbx] = i + 1; num_assigned_to_cbx += t->chain_size[chain_world]; if (num_assigned_to_cbx >= num_surfs_per_cbx) @@ -188,6 +196,7 @@ void R_SetupWorldCBXTexRanges (qboolean use_tasks) { world_texstart[current_cbx] = i + 1; } + num_assigned_to_cbx = 0; } } diff --git a/Quake/sv_main.c b/Quake/sv_main.c index 8006f0faf..3b5c4ca43 100644 --- a/Quake/sv_main.c +++ b/Quake/sv_main.c @@ -3192,7 +3192,7 @@ void SV_SpawnServer (const char *server) qcvm->max_edicts = CLAMP (MIN_EDICTS, (int)max_edicts.value, MAX_EDICTS); // johnfitz -- max_edicts cvar qcvm->edicts = (edict_t *)Mem_Alloc (qcvm->max_edicts * qcvm->edict_size); // ericw -- sv.edicts switched to use malloc() -#ifdef PARANOID +#if defined(DEBUG) || defined(_DEBUG) for (int j = 0; j < qcvm->max_edicts; j++) { // set debug fiels for all max_edicts diff --git a/Quake/sys.h b/Quake/sys.h index f80c30ca7..b577be6c7 100644 --- a/Quake/sys.h +++ b/Quake/sys.h @@ -82,4 +82,10 @@ bool Sys_Pin_Current_Thread (int core_index); // The returned string is a per-thread static buffer (no deallocation needed) const char *Sys_StackTrace (void); +// Return true if we are running in a debugger +bool Sys_IsInDebugger (void); + +// Break in a debugger if we are running in one, else do nothing +void Sys_DebugBreak (void); + #endif /* _QUAKE_SYS_H */ diff --git a/Quake/sys_sdl_unix.c b/Quake/sys_sdl_unix.c index 78352158c..b241a0352 100644 --- a/Quake/sys_sdl_unix.c +++ b/Quake/sys_sdl_unix.c @@ -28,8 +28,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include #include +#include #ifdef PLATFORM_OSX #include /* dirname() and basename() */ +#include #endif #include #include @@ -254,20 +256,26 @@ void Sys_Error (const char *error, ...) va_list argptr; char text[4096]; - PR_SwitchQCVM (NULL); host_parms->errstate++; va_start (argptr, error); q_vsnprintf (text, sizeof (text), error, argptr); va_end (argptr); - fputs (errortxt1, stderr); + Sys_DebugBreak (); + + if (!Sys_IsInDebugger ()) + q_snprintf (text + strnlen (text, sizeof (text)), sizeof (text), "\nSTACK TRACE:\n%s", Sys_StackTrace ()); + + PR_SwitchQCVM (NULL); + + fputs (errortxt1, stdout); Host_Shutdown (); - fputs (errortxt2, stderr); - q_snprintf (text + strnlen (text, sizeof (text)), sizeof (text), "\nSTACK TRACE:\n%s", Sys_StackTrace ()); - fputs (text, stderr); - fputs ("\n\n", stderr); - if (!isDedicated) + fputs (errortxt2, stdout); + + Sys_Printf (text, "%s\n\n", text); + + if (!isDedicated && !Sys_IsInDebugger ()) PL_ErrorDialog (text); exit (1); @@ -391,9 +399,20 @@ const char *Sys_StackTrace (void) int nb_frames = backtrace (buffer, MAX_STACK_FRAMES); + // display on 1 line to pass to addr2line easily: + for (int frame_index = 0; frame_index < nb_frames; frame_index++) + { + q_snprintf (output_buffer + strnlen (output_buffer, OUTPUT_BUFFER_SIZE), OUTPUT_BUFFER_SIZE, "0x%" PRIxPTR " ", (uintptr_t)buffer[frame_index]); + if (frame_index == nb_frames - 1) + q_snprintf (output_buffer + strnlen (output_buffer, OUTPUT_BUFFER_SIZE), OUTPUT_BUFFER_SIZE, "\n"); + } + + // Then print 1 frame per line, together with its symbol using backtrace_symbols() + // This is not working on MacOS, skip it and only rely on addr2line to solve addresses +#if !defined(PLATFORM_OSX) + char **symbols = backtrace_symbols (buffer, nb_frames); - // Print 1 frame per line, together with its symbol for (int frame_index = 0; frame_index < nb_frames; frame_index++) { // this is not super-safe @@ -403,9 +422,55 @@ const char *Sys_StackTrace (void) } free (symbols); +#endif return output_buffer; #undef MAX_STACK_FRAMES #undef OUTPUT_BUFFER_SIZE } + +bool Sys_IsInDebugger (void) +{ +#if !defined(PLATFORM_OSX) + FILE *f = fopen ("/proc/self/status", "r"); + if (!f) + return false; + + char line[256]; + + while (fgets (line, sizeof (line), f)) + { + if (strncmp (line, "TracerPid:", 10) == 0) + { + int pid = atoi (line + 10); + fclose (f); + return pid != 0; + } + } + fclose (f); +#else + int mib[4]; + struct kinfo_proc info; + size_t size = sizeof (info); + info.kp_proc.p_flag = 0; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid (); + + if (sysctl (mib, 4, &info, &size, NULL, 0) == -1) + return false; + + return (info.kp_proc.p_flag & P_TRACED) != 0; +#endif + + return false; +} + +void Sys_DebugBreak (void) +{ + if (Sys_IsInDebugger ()) + raise (SIGTRAP); +} diff --git a/Quake/sys_sdl_win.c b/Quake/sys_sdl_win.c index 2cd2ed935..ec65ea613 100644 --- a/Quake/sys_sdl_win.c +++ b/Quake/sys_sdl_win.c @@ -235,21 +235,26 @@ void Sys_Error (const char *error, ...) q_vsnprintf (text, sizeof (text), error, argptr); va_end (argptr); + Sys_DebugBreak (); + + if (!Sys_IsInDebugger ()) + q_snprintf (text + strnlen (text, sizeof (text)), sizeof (text), "\nSTACK TRACE:\n%s", Sys_StackTrace ()); + PR_SwitchQCVM (NULL); if (isDedicated) WriteFile (houtput, errortxt1, strlen (errortxt1), &dummy, NULL); /* SDL will put these into its own stderr log, so print to stderr even in graphical mode. */ - fputs (errortxt1, stderr); - fputs (errortxt2, stderr); + fputs (errortxt1, stdout); + fputs (errortxt2, stdout); - q_snprintf (text + strnlen (text, sizeof (text)), sizeof (text), "\nSTACK TRACE:\n%s", Sys_StackTrace ()); + Sys_Printf (text, "%s\n\n", text); - fputs (text, stderr); - fputs ("\n\n", stderr); - if (!isDedicated) + if (!isDedicated && !Sys_IsInDebugger ()) + { PL_ErrorDialog (text); + } else { WriteFile (houtput, errortxt2, strlen (errortxt2), &dummy, NULL); @@ -258,17 +263,13 @@ void Sys_Error (const char *error, ...) SDL_Delay (3000); /* show the console 3 more seconds */ } -#ifdef _DEBUG - __debugbreak (); -#endif - exit (1); } void Sys_Printf (const char *fmt, ...) { va_list argptr; - char text[1024]; + char text[4096]; DWORD dummy; va_start (argptr, fmt); @@ -509,3 +510,18 @@ const char *Sys_StackTrace (void) #undef MAX_STACK_FRAMES #undef OUTPUT_BUFFER_SIZE } + +bool Sys_IsInDebugger (void) +{ + // skip the pop-up when in a debugger: + BOOL debugger_attached = FALSE; + CheckRemoteDebuggerPresent (GetCurrentProcess (), &debugger_attached); + + return debugger_attached != 0; +} + +void Sys_DebugBreak (void) +{ + if (Sys_IsInDebugger ()) + DebugBreak (); +}