diff --git a/src/d_main.cpp b/src/d_main.cpp
index dad2b86d9..bffb4136d 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -1027,6 +1027,12 @@ void D_DoomLoop ()
 			}
 			D_ErrorCleanup ();
 		}
+		catch (CVMAbortException &error)
+		{
+			error.MaybePrintMessage();
+			Printf("%s", error.stacktrace);
+			D_ErrorCleanup();
+		}
 	}
 }
 
diff --git a/src/info.cpp b/src/info.cpp
index a0117a71f..d3cb4e274 100644
--- a/src/info.cpp
+++ b/src/info.cpp
@@ -89,16 +89,35 @@ bool FState::CallAction(AActor *self, AActor *stateowner, FStateParamInfo *info,
 				stateret = NULL;
 			}
 		}
-		if (stateret == NULL)
+		try
 		{
-			GlobalVMStack.Call(ActionFunc, params, ActionFunc->ImplicitArgs, NULL, 0, NULL);
+			if (stateret == NULL)
+			{
+				GlobalVMStack.Call(ActionFunc, params, ActionFunc->ImplicitArgs, NULL, 0, NULL);
+			}
+			else
+			{
+				VMReturn ret;
+				ret.PointerAt((void **)stateret);
+				GlobalVMStack.Call(ActionFunc, params, ActionFunc->ImplicitArgs, &ret, 1, NULL);
+			}
 		}
-		else
+		catch (CVMAbortException &err)
 		{
-			VMReturn ret;
-			ret.PointerAt((void **)stateret);
-			GlobalVMStack.Call(ActionFunc, params, ActionFunc->ImplicitArgs, &ret, 1, NULL);
+			err.MaybePrintMessage();
+			auto owner = FState::StaticFindStateOwner(this);
+			int offs = int(this - owner->OwnedStates);
+			const char *callinfo = "";
+			if (info != nullptr && info->mStateType == STATE_Psprite)
+			{
+				if (stateowner->IsKindOf(RUNTIME_CLASS(AWeapon)) && stateowner != self) callinfo = "weapon ";
+				else callinfo = "overlay ";
+			}
+			err.stacktrace.AppendFormat("Called from %sstate %s.%d in %s\n", callinfo, owner->TypeName.GetChars(), offs, stateowner->GetClass()->TypeName.GetChars());
+			throw;
+			throw;
 		}
+
 		ActionCycles.Unclock();
 		return true;
 	}
diff --git a/src/p_actionfunctions.cpp b/src/p_actionfunctions.cpp
index 83844ffd7..12645561b 100644
--- a/src/p_actionfunctions.cpp
+++ b/src/p_actionfunctions.cpp
@@ -183,7 +183,19 @@ bool ACustomInventory::CallStateChain (AActor *actor, FState *state)
 					numret = 2;
 				}
 			}
-			GlobalVMStack.Call(state->ActionFunc, params, state->ActionFunc->ImplicitArgs, wantret, numret);
+			try
+			{
+				GlobalVMStack.Call(state->ActionFunc, params, state->ActionFunc->ImplicitArgs, wantret, numret);
+			}
+			catch (CVMAbortException &err)
+			{
+				err.MaybePrintMessage();
+				auto owner = FState::StaticFindStateOwner(state);
+				int offs = int(state - owner->OwnedStates);
+				err.stacktrace.AppendFormat("Called from state %s.%d in inventory state chain in %s\n", owner->TypeName.GetChars(), offs, GetClass()->TypeName.GetChars());
+				throw;
+			}
+
 			// As long as even one state succeeds, the whole chain succeeds unless aborted below.
 			// A state that wants to jump does not count as "succeeded".
 			if (nextstate == NULL)
diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h
index aeb52dabb..1869c410b 100644
--- a/src/scripting/vm/vm.h
+++ b/src/scripting/vm/vm.h
@@ -194,7 +194,8 @@ class CVMAbortException : public CDoomError
 {
 public:
 	static FString stacktrace;
-	CVMAbortException(EVMAbortException reason, const char *moreinfo, ...);
+	CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap);
+	void MaybePrintMessage();
 };
 
 enum EVMOpMode
@@ -808,6 +809,12 @@ union FVoidObj
 	void *v;
 };
 
+struct FStatementInfo
+{
+	uint16_t InstructionIndex;
+	uint16_t LineNumber;
+};
+
 class VMScriptFunction : public VMFunction
 {
 	DECLARE_CLASS(VMScriptFunction, VMFunction);
@@ -815,12 +822,13 @@ public:
 	VMScriptFunction(FName name=NAME_None);
 	~VMScriptFunction();
 	size_t PropagateMark();
-	void Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta);
+	void Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers);
 
 	VM_ATAG *KonstATags() { return (VM_UBYTE *)(KonstA + NumKonstA); }
 	const VM_ATAG *KonstATags() const { return (VM_UBYTE *)(KonstA + NumKonstA); }
 
 	VMOP *Code;
+	FStatementInfo *LineInfo;
 	FString SourceFileName;
 	int *KonstD;
 	double *KonstF;
@@ -828,6 +836,7 @@ public:
 	FVoidObj *KonstA;
 	int ExtraSpace;
 	int CodeSize;			// Size of code in instructions (not bytes)
+	unsigned LineInfoCount;
 	VM_UBYTE NumRegD;
 	VM_UBYTE NumRegF;
 	VM_UBYTE NumRegS;
@@ -843,6 +852,7 @@ public:
 	void InitExtra(void *addr);
 	void DestroyExtra(void *addr);
 	int AllocExtraStack(PType *type);
+	int PCToLine(const VMOP *pc);
 };
 
 class VMFrameStack
diff --git a/src/scripting/vm/vmbuilder.cpp b/src/scripting/vm/vmbuilder.cpp
index 65ec0bb26..3e833af79 100644
--- a/src/scripting/vm/vmbuilder.cpp
+++ b/src/scripting/vm/vmbuilder.cpp
@@ -88,7 +88,7 @@ void VMFunctionBuilder::BeginStatement(FxExpression *stmt)
 	// only add a new entry if the line number differs.
 	if (LineNumbers.Size() == 0 || stmt->ScriptPosition.ScriptLine != LineNumbers.Last().LineNumber)
 	{
-		StatementInfo si = { (uint16_t)Code.Size(), (uint16_t)stmt->ScriptPosition.ScriptLine };
+		FStatementInfo si = { (uint16_t)Code.Size(), (uint16_t)stmt->ScriptPosition.ScriptLine };
 		LineNumbers.Push(si);
 	}
 	StatementStack.Push(stmt);
@@ -102,17 +102,18 @@ void VMFunctionBuilder::EndStatement()
 	// Re-enter the previous statement.
 	if (StatementStack.Size() > 0)
 	{
-		StatementInfo si = { (uint16_t)Code.Size(), (uint16_t)StatementStack.Last()->ScriptPosition.ScriptLine };
+		FStatementInfo si = { (uint16_t)Code.Size(), (uint16_t)StatementStack.Last()->ScriptPosition.ScriptLine };
 		LineNumbers.Push(si);
 	}
 }
 
 void VMFunctionBuilder::MakeFunction(VMScriptFunction *func)
 {
-	func->Alloc(Code.Size(), IntConstantList.Size(), FloatConstantList.Size(), StringConstantList.Size(), AddressConstantList.Size());
+	func->Alloc(Code.Size(), IntConstantList.Size(), FloatConstantList.Size(), StringConstantList.Size(), AddressConstantList.Size(), LineNumbers.Size());
 
 	// Copy code block.
 	memcpy(func->Code, &Code[0], Code.Size() * sizeof(VMOP));
+	memcpy(func->LineInfo, &LineNumbers[0], LineNumbers.Size() * sizeof(LineNumbers[0]));
 
 	// Create constant tables.
 	if (IntConstantList.Size() > 0)
diff --git a/src/scripting/vm/vmbuilder.h b/src/scripting/vm/vmbuilder.h
index d319b2280..7c086ac0c 100644
--- a/src/scripting/vm/vmbuilder.h
+++ b/src/scripting/vm/vmbuilder.h
@@ -100,13 +100,7 @@ private:
 		VM_ATAG Tag;
 	};
 
-	struct StatementInfo
-	{
-		uint16_t InstructionIndex;
-		uint16_t LineNumber;
-	};
-
-	TArray<StatementInfo> LineNumbers;
+	TArray<FStatementInfo> LineNumbers;
 	TArray<FxExpression *> StatementStack;
 
 	TArray<int> IntConstantList;
diff --git a/src/scripting/vm/vmexec.cpp b/src/scripting/vm/vmexec.cpp
index 198410864..79d0b7fe6 100644
--- a/src/scripting/vm/vmexec.cpp
+++ b/src/scripting/vm/vmexec.cpp
@@ -40,6 +40,11 @@
 #include "textures/textures.h"
 #include "math/cmath.h"
 
+// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown.
+void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
+// intentionally implemented in a different source file tp prevent inlining.
+void ThrowVMException(VMException *x);
+
 #define IMPLEMENT_VMEXEC
 
 #if !defined(COMPGOTO) && defined(__GNUC__)
@@ -91,7 +96,7 @@
 	}
 
 #define GETADDR(a,o,x) \
-	if (a == NULL) { throw CVMAbortException(x, nullptr); } \
+	if (a == NULL) { ThrowAbortException(x, nullptr); } \
 	ptr = (VM_SBYTE *)a + o
 
 static const VM_UWORD ZapTable[16] =
@@ -227,57 +232,4 @@ void VMFillParams(VMValue *params, VMFrame *callee, int numparam)
 	}
 }
 
-void NullParam(const char *varname)
-{
-	throw CVMAbortException(X_READ_NIL, "In function parameter %s", varname);
-}
-
-FString CVMAbortException::stacktrace;
-
-CVMAbortException::CVMAbortException(EVMAbortException reason, const char *moreinfo, ...)
-{
-	SetMessage("VM execution aborted: ");
-	switch (reason)
-	{
-	case X_READ_NIL:
-		AppendMessage("tried to read from address zero.");
-		break;
-
-	case X_WRITE_NIL:
-		AppendMessage("tried to write to address zero.");
-		break;
-
-	case X_TOO_MANY_TRIES:
-		AppendMessage("too many try-catch blocks.");
-		break;
-
-	case X_ARRAY_OUT_OF_BOUNDS:
-		AppendMessage("array access out of bounds.");
-		break;
-
-	case X_DIVISION_BY_ZERO:
-		AppendMessage("division by zero.");
-		break;
-
-	case X_BAD_SELF:
-		AppendMessage("invalid self pointer.");
-		break;
-
-	default:
-	{
-		size_t len = strlen(m_Message);
-		mysnprintf(m_Message + len, MAX_ERRORTEXT - len, "Unknown reason %d", reason);
-		break;
-	}
-	}
-	if (moreinfo != nullptr)
-	{
-		AppendMessage(" ");
-		va_list ap;
-		va_start(ap, moreinfo);
-		size_t len = strlen(m_Message);
-		myvsnprintf(m_Message + len, MAX_ERRORTEXT - len, moreinfo, ap);
-		va_end(ap);
-	}
-}
 
diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h
index 1e688365d..4152facc1 100644
--- a/src/scripting/vm/vmexec.h
+++ b/src/scripting/vm/vmexec.h
@@ -2,7 +2,6 @@
 #error vmexec.h must not be #included outside vmexec.cpp. Use vm.h instead.
 #endif
 
-
 static int Exec(VMFrameStack *stack, const VMOP *pc, VMReturn *ret, int numret)
 {
 #if COMPGOTO
@@ -596,7 +595,17 @@ begin:
 			FillReturns(reg, f, returns, pc+1, C);
 			if (call->Native)
 			{
-				numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C);
+				try
+				{
+					numret = static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, returns, C);
+				}
+				catch (CVMAbortException &err)
+				{
+					err.MaybePrintMessage();
+					err.stacktrace.AppendFormat("Called from %s\n", call->PrintableName.GetChars());
+					// PrintParameters(reg.param + f->NumParam - B, B);
+					throw;
+				}
 			}
 			else
 			{
@@ -640,7 +649,17 @@ begin:
 
 			if (call->Native)
 			{
-				return static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, ret, numret);
+				try
+				{
+					return static_cast<VMNativeFunction *>(call)->NativeCall(reg.param + f->NumParam - B, call->DefaultArgs, B, ret, numret);
+				}
+				catch (CVMAbortException &err)
+				{
+					err.MaybePrintMessage();
+					err.stacktrace.AppendFormat("Called from %s\n", call->PrintableName.GetChars());
+					// PrintParameters(reg.param + f->NumParam - B, B);
+					throw;
+				}
 			}
 			else
 			{ // FIXME: Not a true tail call
@@ -704,7 +723,7 @@ begin:
 		assert(try_depth < MAX_TRY_DEPTH);
 		if (try_depth >= MAX_TRY_DEPTH)
 		{
-			throw CVMAbortException(X_TOO_MANY_TRIES, nullptr);
+			ThrowAbortException(X_TOO_MANY_TRIES, nullptr);
 		}
 		assert((pc + JMPOFS(pc) + 1)->op == OP_CATCH);
 		exception_frames[try_depth++] = pc + JMPOFS(pc) + 1;
@@ -717,17 +736,17 @@ begin:
 		if (a == 0)
 		{
 			ASSERTA(B);
-			throw((VMException *)reg.a[B]);
+			ThrowVMException((VMException *)reg.a[B]);
 		}
 		else if (a == 1)
 		{
 			ASSERTKA(B);
 			assert(konstatag[B] == ATAG_OBJECT);
-			throw((VMException *)konsta[B].o);
+			ThrowVMException((VMException *)konsta[B].o);
 		}
 		else
 		{
-			throw CVMAbortException(EVMAbortException(BC), nullptr);
+			ThrowAbortException(EVMAbortException(BC), nullptr);
 		}
 		NEXTOP;
 	OP(CATCH):
@@ -739,7 +758,7 @@ begin:
 	OP(BOUND):
 		if (reg.d[a] >= BC)
 		{
-			throw CVMAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", BC, reg.d[a]);
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", BC, reg.d[a]);
 		}
 		NEXTOP;
 
@@ -747,7 +766,7 @@ begin:
 		ASSERTKD(BC);
 		if (reg.d[a] >= konstd[BC])
 		{
-			throw CVMAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", konstd[BC], reg.d[a]);
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", konstd[BC], reg.d[a]);
 		}
 		NEXTOP;
 
@@ -755,7 +774,7 @@ begin:
 		ASSERTD(B);
 		if (reg.d[a] >= reg.d[B])
 		{
-			throw CVMAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", reg.d[B], reg.d[a]);
+			ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "Max.index = %u, current index = %u\n", reg.d[B], reg.d[a]);
 		}
 		NEXTOP;
 
@@ -902,7 +921,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = reg.d[B] / reg.d[C];
 		NEXTOP;
@@ -910,7 +929,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
 		if (konstd[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = reg.d[B] / konstd[C];
 		NEXTOP;
@@ -918,7 +937,7 @@ begin:
 		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = konstd[B] / reg.d[C];
 		NEXTOP;
@@ -927,7 +946,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)reg.d[B] / (unsigned)reg.d[C]);
 		NEXTOP;
@@ -935,7 +954,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
 		if (konstd[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)reg.d[B] / (unsigned)konstd[C]);
 		NEXTOP;
@@ -943,7 +962,7 @@ begin:
 		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)konstd[B] / (unsigned)reg.d[C]);
 		NEXTOP;
@@ -952,7 +971,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = reg.d[B] % reg.d[C];
 		NEXTOP;
@@ -960,7 +979,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
 		if (konstd[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = reg.d[B] % konstd[C];
 		NEXTOP;
@@ -968,7 +987,7 @@ begin:
 		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = konstd[B] % reg.d[C];
 		NEXTOP;
@@ -977,7 +996,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)reg.d[B] % (unsigned)reg.d[C]);
 		NEXTOP;
@@ -985,7 +1004,7 @@ begin:
 		ASSERTD(a); ASSERTD(B); ASSERTKD(C);
 		if (konstd[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)reg.d[B] % (unsigned)konstd[C]);
 		NEXTOP;
@@ -993,7 +1012,7 @@ begin:
 		ASSERTD(a); ASSERTKD(B); ASSERTD(C);
 		if (reg.d[C] == 0)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.d[a] = int((unsigned)konstd[B] % (unsigned)reg.d[C]);
 		NEXTOP;
@@ -1171,7 +1190,7 @@ begin:
 		ASSERTF(a); ASSERTF(B); ASSERTF(C);
 		if (reg.f[C] == 0.)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.f[a] = reg.f[B] / reg.f[C];
 		NEXTOP;
@@ -1179,7 +1198,7 @@ begin:
 		ASSERTF(a); ASSERTF(B); ASSERTKF(C);
 		if (konstf[C] == 0.)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.f[a] = reg.f[B] / konstf[C];
 		NEXTOP;
@@ -1187,7 +1206,7 @@ begin:
 		ASSERTF(a); ASSERTKF(B); ASSERTF(C);
 		if (reg.f[C] == 0.)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.f[a] = konstf[B] / reg.f[C];
 		NEXTOP;
@@ -1198,7 +1217,7 @@ begin:
 	Do_MODF:
 		if (fc == 0.)
 		{
-			throw CVMAbortException(X_DIVISION_BY_ZERO, nullptr);
+			ThrowAbortException(X_DIVISION_BY_ZERO, nullptr);
 		}
 		reg.f[a] = luai_nummod(fb, fc);
 		NEXTOP;
@@ -1611,6 +1630,13 @@ begin:
 		// Nothing caught it. Rethrow and let somebody else deal with it.
 		throw;
 	}
+	catch (CVMAbortException &err)
+	{
+		err.MaybePrintMessage();
+		err.stacktrace.AppendFormat("Called from %s at %s, line %d\n", sfunc->PrintableName.GetChars(), sfunc->SourceFileName.GetChars(), sfunc->PCToLine(pc));
+		// PrintParameters(reg.param + f->NumParam - B, B);
+		throw;
+	}
 	return 0;
 }
 
diff --git a/src/scripting/vm/vmframe.cpp b/src/scripting/vm/vmframe.cpp
index 3a7d9b3bf..f9f04e425 100644
--- a/src/scripting/vm/vmframe.cpp
+++ b/src/scripting/vm/vmframe.cpp
@@ -3,6 +3,7 @@
 **
 **---------------------------------------------------------------------------
 ** Copyright -2016 Randy Heit
+** Copyright 2016 Christoph Oelckers
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without
@@ -33,6 +34,7 @@
 
 #include <new>
 #include "dobject.h"
+#include "v_text.h"
 
 IMPLEMENT_CLASS(VMException, false, false)
 IMPLEMENT_CLASS(VMFunction, true, true)
@@ -48,11 +50,13 @@ VMScriptFunction::VMScriptFunction(FName name)
 {
 	Native = false;
 	Name = name;
+	LineInfo = nullptr;
 	Code = NULL;
 	KonstD = NULL;
 	KonstF = NULL;
 	KonstS = NULL;
 	KonstA = NULL;
+	LineInfoCount = 0;
 	ExtraSpace = 0;
 	CodeSize = 0;
 	NumRegD = 0;
@@ -82,7 +86,7 @@ VMScriptFunction::~VMScriptFunction()
 	}
 }
 
-void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta)
+void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numkonsts, int numkonsta, int numlinenumbers)
 {
 	assert(Code == NULL);
 	assert(numops > 0);
@@ -90,14 +94,27 @@ void VMScriptFunction::Alloc(int numops, int numkonstd, int numkonstf, int numko
 	assert(numkonstf >= 0 && numkonstf <= 65535);
 	assert(numkonsts >= 0 && numkonsts <= 65535);
 	assert(numkonsta >= 0 && numkonsta <= 65535);
+	assert(numlinenumbers >= 0 && numlinenumbers <= 65535);
 	void *mem = M_Malloc(numops * sizeof(VMOP) +
 						 numkonstd * sizeof(int) +
 						 numkonstf * sizeof(double) +
 						 numkonsts * sizeof(FString) +
-						 numkonsta * (sizeof(FVoidObj) + 1));
+						 numkonsta * (sizeof(FVoidObj) + 1) +
+						 numlinenumbers * sizeof(FStatementInfo));
 	Code = (VMOP *)mem;
 	mem = (void *)((VMOP *)mem + numops);
 
+	if (numlinenumbers > 0)
+	{
+		LineInfo = (FStatementInfo*)mem;
+		LineInfoCount = numlinenumbers;
+		mem = LineInfo + numlinenumbers;
+	}
+	else
+	{
+		LineInfo = nullptr;
+		LineInfoCount = 0;
+	}
 	if (numkonstd > 0)
 	{
 		KonstD = (int *)mem;
@@ -190,6 +207,20 @@ int VMScriptFunction::AllocExtraStack(PType *type)
 	return address;
 }
 
+int VMScriptFunction::PCToLine(const VMOP *pc)
+{
+	int PCIndex = int(pc - Code);
+	if (LineInfoCount == 1) return LineInfo[0].LineNumber;
+	for (unsigned i = 1; i < LineInfoCount; i++)
+	{
+		if (LineInfo[i].InstructionIndex > PCIndex)
+		{
+			return LineInfo[i - 1].LineNumber;
+		}
+	}
+	return -1;
+}
+
 //===========================================================================
 //
 // VMFrame :: InitRegS
@@ -453,20 +484,6 @@ int VMFrameStack::Call(VMFunction *func, VMValue *params, int numparams, VMRetur
 		}
 		throw;
 	}
-	catch (CVMAbortException &exception)
-	{
-		if (allocated)
-		{
-			PopFrame();
-		}
-		if (trap != nullptr)
-		{
-			*trap = nullptr;
-		}
-
-		Printf("%s\n", exception.GetMessage());
-		return -1;
-	}
 	catch (...)
 	{
 		if (allocated)
@@ -476,3 +493,83 @@ int VMFrameStack::Call(VMFunction *func, VMValue *params, int numparams, VMRetur
 		throw;
 	}
 }
+
+// Exception stuff for the VM is intentionally placed there, because having this in vmexec.cpp would subject it to inlining
+// which we do not want because it increases the local stack requirements of Exec which are already too high.
+FString CVMAbortException::stacktrace;
+
+CVMAbortException::CVMAbortException(EVMAbortException reason, const char *moreinfo, va_list ap)
+{
+	SetMessage("VM execution aborted: ");
+	switch (reason)
+	{
+	case X_READ_NIL:
+		AppendMessage("tried to read from address zero.");
+		break;
+
+	case X_WRITE_NIL:
+		AppendMessage("tried to write to address zero.");
+		break;
+
+	case X_TOO_MANY_TRIES:
+		AppendMessage("too many try-catch blocks.");
+		break;
+
+	case X_ARRAY_OUT_OF_BOUNDS:
+		AppendMessage("array access out of bounds.");
+		break;
+
+	case X_DIVISION_BY_ZERO:
+		AppendMessage("division by zero.");
+		break;
+
+	case X_BAD_SELF:
+		AppendMessage("invalid self pointer.");
+		break;
+
+	default:
+	{
+		size_t len = strlen(m_Message);
+		mysnprintf(m_Message + len, MAX_ERRORTEXT - len, "Unknown reason %d", reason);
+		break;
+	}
+	}
+	if (moreinfo != nullptr)
+	{
+		AppendMessage(" ");
+		size_t len = strlen(m_Message);
+		myvsnprintf(m_Message + len, MAX_ERRORTEXT - len, moreinfo, ap);
+	}
+	stacktrace = "";
+}
+
+// Print this only once on the first catch block.
+void CVMAbortException::MaybePrintMessage()
+{
+	auto m = GetMessage();
+	if (m != nullptr)
+	{
+		Printf(TEXTCOLOR_RED);
+		Printf("%s\n", m);
+		SetMessage("");
+	}
+}
+
+
+void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
+{
+	va_list ap;
+	va_start(ap, moreinfo);
+	throw CVMAbortException(reason, moreinfo, ap);
+	va_end(ap);
+}
+
+void NullParam(const char *varname)
+{
+	ThrowAbortException(X_READ_NIL, "In function parameter %s", varname);
+}
+
+void ThrowVMException(VMException *x)
+{
+	throw x;
+}