Commits

spencercw committed 226c7ee

Improve stack trace using debug symbols and tidy up CDB parser implementation.

  • Participants
  • Parent commits 7c73f24
  • Branches debugger

Comments (0)

Files changed (5)

File GbDebuggerMsvs/stack_frame.cpp

 #include "stack_frame.h"
 
 namespace fs = boost::filesystem;
+using std::dec;
 using std::hex;
 using std::setfill;
 using std::setw;
 
 	if (fields & FIF_FUNCNAME)
 	{
+		// Format the function name
 		wostringstream oss;
-		oss << hex << setfill(L'0');
+		oss << setfill(L'0');
+
+		// Module
 		if (fields & FIF_FUNCNAME_MODULE)
 		{
 			oss << module.native() << L"!";
 		}
-		if (!frame_.function.empty())
+
+		// Function name (or address if not available)
+		if (frame_.hasDebugInfo)
 		{
-			CComBSTR str(frame_.function.c_str());
+			assert(!frame_.sourceLocation.function.empty());
+			CComBSTR str(frame_.sourceLocation.function.c_str());
 			oss << str.m_str;
 		}
 		else
 		{
-			oss << setw(4) << static_cast<unsigned>(frame_.functionAddr);
+			oss << hex << setw(4) << static_cast<unsigned>(frame_.functionAddr);
 		}
 		oss << L"()";
-		if (fields & FIF_FUNCNAME_OFFSET && frame_.functionAddr != frame_.instructionAddr)
+
+		// Offset
+		if (fields & FIF_FUNCNAME_LINES && frame_.hasDebugInfo)
 		{
-			int offset = frame_.instructionAddr - frame_.functionAddr;
-			if (offset > 0)
+			oss << dec << L"  Line " << frame_.sourceLocation.line;
+			if (fields & FIF_FUNCNAME_OFFSET && frame_.sourceLocation.lineOffset)
 			{
-				oss << L"  + 0x" << offset << L" bytes";
+				if (frame_.sourceLocation.lineOffset > 0)
+				{
+					oss << hex << L" + 0x" << frame_.sourceLocation.lineOffset << L" bytes";
+				}
+				else
+				{
+					oss << hex << L" - 0x" << -frame_.sourceLocation.lineOffset << L" bytes";
+				}
+			}
+		}
+		else if (fields & FIF_FUNCNAME_OFFSET && frame_.functionAddr != frame_.instructionAddr)
+		{
+			int64_t offset;
+			if (frame_.hasDebugInfo)
+			{
+				offset = frame_.sourceLocation.functionOffset;
 			}
 			else
 			{
-				oss << L"  - 0x" << -offset << L" bytes";
+				offset = frame_.instructionAddr - frame_.functionAddr;
+			}
+
+			if (offset > 0)
+			{
+				oss << hex << L"  + 0x" << offset << L" bytes";
+			}
+			else
+			{
+				oss << hex << L"  - 0x" << -offset << L" bytes";
 			}
 		}
+
 		wstring functionName = oss.str();
 
 		frameInfo->m_dwValidFields |= FIF_FUNCNAME;
 		frameInfo->m_bstrFuncName = SysAllocString(functionName.c_str());
 	}
+	if (fields & FIF_LANGUAGE && frame_.hasDebugInfo && !frame_.sourceLocation.sourceFile.empty())
+	{
+		frameInfo->m_dwValidFields |= FIF_LANGUAGE;
+		switch (frame_.sourceLocation.language)
+		{
+		case CdbFile::SourceLocation::ASSEMBLY:
+			frameInfo->m_bstrLanguage = SysAllocString(L"Assembly");
+			break;
+		case CdbFile::SourceLocation::C:
+			frameInfo->m_bstrLanguage = SysAllocString(L"C");
+			break;
+		default:
+			assert(!"all languages should have been handled");
+		}
+	}
 	if (fields & FIF_MODULE)
 	{
 		frameInfo->m_dwValidFields |= FIF_MODULE;
 	if (fields & FIF_DEBUGINFO)
 	{
 		frameInfo->m_dwValidFields |= FIF_DEBUGINFO;
-		frameInfo->m_fHasDebugInfo = false;
+		frameInfo->m_fHasDebugInfo = frame_.hasDebugInfo;
 	}
 	if (fields & FIF_DEBUG_MODULEP)
 	{

File gb_emulator/include/gb_emulator/cdb_file.h

     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
-
-#ifndef CDB_FILE_H_FB4E2920_0494_11E1_AC8C_0002A5D5C51B
-#define CDB_FILE_H_FB4E2920_0494_11E1_AC8C_0002A5D5C51B
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include <boost/filesystem/path.hpp>
-#include <boost/regex.hpp>
-
-//! Class for accessing the debug information stored within a CDB file.
-/**
- * CDB files are generated by the SDCC compiler and contain detailed information about the compiled
- * program, including function signatures and mappings to source code locations.
- */
-class CdbFile
-{
-public:
-	//! Possible results from calling parseFile().
-	enum ParseError
-	{
-		PARSE_OK,             //!< The file was successfully parsed.
-		NO_SUCH_FILE,         //!< The given file does not exist.
-		OPEN_FAILED,          //!< Failed to open the given file.
-		READ_ERROR,           //!< There was an error reading the file.
-		BAD_RECORD_TYPE,      //!< A record with an unknown type was seen in the file.
-		INVALID_LINE          //!< An invalid line was read from the file.
-	};
-
-	//! Possible scopes for symbols.
-	enum SymbolScope
-	{
-		GLOBAL,               //! Symbol has global scope.
-		FILE,                 //! Symbol has file local scope.
-		FUNCTION              //! Symbol has function local scope.
-	};
-
-	//! Possible address spaces for symbols.
-	enum SymbolAddressSpace
-	{
-		EXTERNAL_STACK,       //! External stack.
-		INTERNAL_STACK,       //! Internal stack.
-		CODE,                 //! Code.
-		CODE_STATIC_SEGMENT,  //! Code / static segment.
-		INTERNAL_RAM_LOW,     //! Internal RAM (lower 128 bytes).
-		EXTERNAL_RAM,         //! External RAM.
-		INTERNAL_RAM,         //! Internal RAM.
-		BIT_ADDRESSABLE,      //! Bit addressable.
-		SFR_SPACE,            //! SFR space.
-		SBIT_SPACE,           //! SBIT space.
-		REGISTER_SPACE,       //! Register space.
-		UNDEFINED_SPACE       //! Used for function records or any undefined space code.
-	};
-
-	//! Struct containing debug information about a symbol.
-	struct Symbol
-	{
-		//! Scope of the symbol.
-		SymbolScope scope;
-		//! Further detail about the scope.
-		/**
-		 * The value of this depends on \c scope. If \c scope is \c GLOBAL this string will be
-		 * empty. If \c scope is \c FILE this will contain the filename of the containing file. If
-		 * \c scope is \c FUNCTION this will contain the name of the containing function, if
-		 * available (if not the string will be empty).
-		 */
-		std::string scopeDetail;
-		//! Name of the symbol.
-		std::string name;
-		//! Type of the symbol.
-		std::string type;
-		//! Symbol scope level.
-		unsigned level;
-		//! Symbol scope block.
-		unsigned block;
-		//! Symbol address space.
-		SymbolAddressSpace addressSpace;
-		//! Indicates whether the symbol is on the stack.
-		bool onStack;
-		//! The stack offset relative to the bp variable.
-		/**
-		 * This variable is only valid if \c onStack is \c true.
-		 */
-		int stackOffset;
-		//! The registers allocated to the symbol.
-		/**
-		 * This variable is only valid if \c addressSpace is \c REGISTER_SPACE.
-		 */
-		std::vector<std::string> registers;
-	};
-
-	//! Struct containing debug information about a function.
-	struct Function:
-		public Symbol
-	{
-		//! Indicates whether the function is an interrupt handler.
-		bool isInterrupt;
-		//! The interrupt number.
-		/**
-		 * This variable is only valid if \c isInterrupt is \c true.
-		 */
-		unsigned interruptNumber;
-		//! The register bank number.
-		/**
-		 * This variable is only valid if \c isInterrupt is \c true.
-		 */
-		unsigned registerBank;
-	};
-
-	//! Struct containing the address of a symbol.
-	struct SymbolAddress
-	{
-		//! Scope of the symbol.
-		SymbolScope scope;
-		//! Further detail about the scope.
-		/**
-		 * The value of this depends on \c scope. If \c scope is \c GLOBAL this string will be
-		 * empty. If \c scope is \c FILE this will contain the filename of the containing file. If
-		 * \c scope is \c FUNCTION this will contain the name of the containing function, if
-		 * available (if not the string will be empty).
-		 */
-		std::string scopeDetail;
-		//! Name of the symbol.
-		std::string name;
-		//! Symbol scope level.
-		unsigned level;
-		//! Symbol scope block.
-		unsigned block;
-		//! Address of the symbol relative to the address space specified in the associated symbol
-		//! record.
-		uint64_t address;
-	};
-
-	//! Struct containing mapping information for a line of C source code.
-	struct CLine
-	{
-		//! Filename of the file the line is in.
-		std::string filename;
-		//! Line number.
-		/**
-		 * Line numbers start from 1.
-		 */
-		unsigned line;
-		//! Current level at this line.
-		unsigned level;
-		//! Current block at this line.
-		unsigned block;
-		//! End address of the line.
-		uint64_t endAddress;
-	};
-
-	//! Default constructor.
-	CdbFile();
-
-	//! Parses the given CDB file.
-	ParseError parseFile(const boost::filesystem::path &path);
-
-	//! Finds the name of the function that starts at the given address.
-	/**
-	 * \param address Start address of the function.
-	 * \return The name of the address if found; an empty string otherwise.
-	 */
-	std::string findFunctionName(uint16_t address);
-
-private:
-	bool valid_;
-	std::vector<std::string> modules_;
-	std::map<std::string, Function> functions_;
-	std::map<std::string, Symbol> symbols_;
-	std::map<uint64_t, SymbolAddress> symbolAddresses_;
-	std::map<std::string, SymbolAddress> symbolEndAddresses_;
-	std::vector<CLine> cLines_;
-
-	// Templated functions to extract a symbol or a function from the given line. Use with
-	// T = Symbol or T = Function.
-	template <class T>
-	ParseError parseSymbol(const std::string &line);
-	template <class T>
-	const boost::regex & getSymbolRegex() const;
-	template <class T>
-	void saveSymbol(T &symbol, const boost::smatch &results);
-
-	ParseError parseSymbolScope(
-		const std::string &scopeStr,
-		const boost::ssub_match &file,
-		const boost::ssub_match &function,
-		SymbolScope &scope,
-		std::string &scopeDetail);
-};
-
-#endif
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef CDB_FILE_H_FB4E2920_0494_11E1_AC8C_0002A5D5C51B
+#define CDB_FILE_H_FB4E2920_0494_11E1_AC8C_0002A5D5C51B
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/filesystem/path.hpp>
+#include <boost/regex.hpp>
+
+//! Class for accessing the debug information stored within a CDB file.
+/**
+ * CDB files are generated by the SDCC compiler and contain detailed information about the compiled
+ * program, including function signatures and mappings to source code locations.
+ */
+class CdbFile
+{
+public:
+	//! Possible results from calling parseFile().
+	enum ParseError
+	{
+		PARSE_OK,             //!< The file was successfully parsed.
+		NO_SUCH_FILE,         //!< The given file does not exist.
+		OPEN_FAILED,          //!< Failed to open the given file.
+		READ_ERROR,           //!< There was an error reading the file.
+		BAD_RECORD_TYPE,      //!< A record with an unknown type was seen in the file.
+		INVALID_LINE,         //!< An invalid line was read from the file.
+		DATA_INCONSISTENCY    //!< There is an inconsistency in the data.
+	};
+
+	//! Possible scopes for symbols.
+	enum SymbolScope
+	{
+		GLOBAL,               //! Symbol has global scope.
+		FILE,                 //! Symbol has file local scope.
+		FUNCTION              //! Symbol has function local scope.
+	};
+
+	//! Possible address spaces for symbols.
+	enum SymbolAddressSpace
+	{
+		EXTERNAL_STACK,       //! External stack.
+		INTERNAL_STACK,       //! Internal stack.
+		CODE,                 //! Code.
+		CODE_STATIC_SEGMENT,  //! Code / static segment.
+		INTERNAL_RAM_LOW,     //! Internal RAM (lower 128 bytes).
+		EXTERNAL_RAM,         //! External RAM.
+		INTERNAL_RAM,         //! Internal RAM.
+		BIT_ADDRESSABLE,      //! Bit addressable.
+		SFR_SPACE,            //! SFR space.
+		SBIT_SPACE,           //! SBIT space.
+		REGISTER_SPACE,       //! Register space.
+		UNDEFINED_SPACE       //! Used for function records or any undefined space code.
+	};
+
+	//! Struct containing mapping information for a line of C source code.
+	struct CLine
+	{
+		//! Filename of the file the line is in.
+		std::string filename;
+		//! Line number.
+		/**
+		 * Line numbers start from 1.
+		 */
+		unsigned line;
+		//! Current level at this line.
+		unsigned level;
+		//! Current block at this line.
+		unsigned block;
+		//! End address of the line.
+		uint64_t endAddress;
+	};
+
+	//! Struct containing debug information about a symbol.
+	struct Symbol
+	{
+		//! Scope of the symbol.
+		SymbolScope scope;
+		//! Further detail about the scope.
+		/**
+		 * The value of this depends on \c scope. If \c scope is \c GLOBAL this string will be
+		 * empty. If \c scope is \c FILE this will contain the filename of the containing file. If
+		 * \c scope is \c FUNCTION this will contain the name of the containing function, if
+		 * available (if not the string will be empty).
+		 */
+		std::string scopeDetail;
+		//! Name of the symbol.
+		std::string name;
+		//! Type of the symbol.
+		std::string type;
+		//! Symbol scope level.
+		unsigned level;
+		//! Symbol scope block.
+		unsigned block;
+		//! Symbol address space.
+		SymbolAddressSpace addressSpace;
+		//! Indicates whether the symbol is on the stack.
+		bool onStack;
+		//! The stack offset relative to the bp variable.
+		/**
+		 * This variable is only valid if \c onStack is \c true.
+		 */
+		int stackOffset;
+		//! The registers allocated to the symbol.
+		/**
+		 * This variable is only valid if \c addressSpace is \c REGISTER_SPACE.
+		 */
+		std::vector<std::string> registers;
+
+		//! Indicates whether function data is available for the symbol.
+		/**
+		 * If this value is \c true then \c isInterrupt, \c interruptNumber and \c registerBank are
+		 * meaningful values. Note that this being \c false means only that function data isn't
+		 * available for the symbol; not necessarily that it is not a function.
+		 */
+		bool functionDataValid;
+		//! Indicates whether the function is an interrupt handler.
+		bool isInterrupt;
+		//! The interrupt number.
+		/**
+		 * This variable is only valid if \c isInterrupt is \c true.
+		 */
+		unsigned interruptNumber;
+		//! The register bank number.
+		/**
+		 * This variable is only valid if \c isInterrupt is \c true.
+		 */
+		unsigned registerBank;
+
+		//! Indicates whether the \c address field is valid.
+		bool addressValid;
+		//! Indicates whether the \c endAddress field is valid;
+		bool endAddressValid;
+		//! The address of the symbol, if available.
+		/**
+		 * This is relative to the 
+		 */
+		uint64_t address;
+		//! The end address of the symbol, if available.
+		/**
+		 * Only functions use this field as other symbols have their size information available in
+		 * the type.
+		 */
+		uint64_t endAddress;
+
+		//! Mappings to C lines of source code.
+		/**
+		 * This will be empty if no C source mapping data is available.
+		 */
+		std::vector<CLine> cLines;
+	};
+
+	//! Struct containing details of the source of a particular address.
+	struct SourceLocation
+	{
+		//! Possible source languages.
+		enum Language
+		{
+			ASSEMBLY,
+			C
+		};
+
+		//! Name of the function.
+		std::string function;
+		//! Offset from the beginning of the function in bytes.
+		int64_t functionOffset;
+		//! Source file.
+		/**
+		 * This will be empty if no source information is available.
+		 */
+		std::string sourceFile;
+		//! The language of the source file.
+		/**
+		 * If \c sourceFile is empty this value is unused.
+		 */
+		Language language;
+		//! Line number in the source file.
+		/**
+		 * If \c sourceFile is empty this value is unused.
+		 */
+		unsigned line;
+		//! Offset from the beginning of the line in bytes.
+		/**
+		 * If \c sourceFile is empty this value is unused.
+		 */
+		int64_t lineOffset;
+	};
+
+	//! Default constructor.
+	CdbFile();
+
+	//! Parses the given CDB file.
+	ParseError parseFile(const boost::filesystem::path &path);
+
+	//! Finds the source information for the machine code at the given address.
+	/**
+	 * \param address The address to get the data for.
+	 * \param sourceLocation The struct populated with the data if available.
+	 * \return Whether the data was available. \c sourceLocation is cleared if not.
+	 */
+	bool findSource(uint64_t address, SourceLocation &sourceLocation);
+
+private:
+	bool valid_;
+	std::vector<std::string> modules_;
+	std::map<std::string, Symbol> symbols_;
+
+	ParseError parseSymbol(const std::string &line, bool isFunction, Symbol &symbol);
+
+	// Finds the function which covers the given address, if any.
+	Symbol * findFunction(uint64_t address);
+
+	ParseError parseSymbolScope(
+		const std::string &scopeStr,
+		const boost::ssub_match &file,
+		const boost::ssub_match &function,
+		SymbolScope &scope,
+		std::string &scopeDetail);
+};
+
+#endif

File gb_emulator/include/gb_emulator/gb_debugger.h

 
 struct GB_EMULATOR_API GbStackFrame
 {
+	bool isRom;
 	std::pair<boost::filesystem::path, uint32_t> module;
-	std::string function;
+
+	bool hasDebugInfo;
+	CdbFile::SourceLocation sourceLocation;
+
 	uint16_t stackAddrMin;
 	uint16_t stackAddrMax;
 	uint16_t functionAddr;

File gb_emulator/src/cdb_file.cpp

     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
-
-#include <gb_emulator/cdb_file.h>
-
-#include <iomanip>
-#include <sstream>
-
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
-#include <boost/filesystem/fstream.hpp>
-#include <boost/filesystem/operations.hpp>
-#include <boost/lexical_cast.hpp>
-
-namespace fs = boost::filesystem;
-using boost::algorithm::is_any_of;
-using boost::lexical_cast;
-using boost::token_compress_on;
-using std::hex;
-using std::ios_base;
-using std::istream;
-using std::istringstream;
-using std::make_pair;
-using std::map;
-using std::string;
-using std::vector;
-
-istream & getLine(istream &is, string &line)
-{
-	line.clear();
-	char ch;
-	while (is.get(ch))
-	{
-		if (ch == '\n')
-		{
-			break;
-		}
-		else if (ch == '\r')
-		{
-			if (is.peek() == '\n')
-			{
-				is.ignore();
-			}
-			break;
-		}
-		else
-		{
-			line.push_back(ch);
-		}
-	}
-	return is;
-}
-
-CdbFile::CdbFile():
-valid_(false)
-{
-}
-
-CdbFile::ParseError CdbFile::parseFile(const fs::path &path)
-{
-	// Clear any existing parsed data
-	modules_.clear();
-	functions_.clear();
-	symbols_.clear();
-	symbolAddresses_.clear();
-	symbolEndAddresses_.clear();
-	cLines_.clear();
-
-	// Open the file
-	if (!fs::exists(path))
-	{
-		return NO_SUCH_FILE;
-	}
-	fs::ifstream f(path, ios_base::in | ios_base::binary);
-	if (!f)
-	{
-		return OPEN_FAILED;
-	}
-
-	// Parse the file
-	string line;
-	while (getLine(f, line))
-	{
-		// Ignore empty lines
-		if (line.empty())
-		{
-			continue;
-		}
-
-		// Switch on the record type
-		switch (line[0])
-		{
-		// Module record
-		case 'M':
-			{
-				static const boost::regex regex("M:(.+?)");
-				boost::smatch results;
-				if (!regex_match(line, results, regex))
-				{
-					return INVALID_LINE;
-				}
-				modules_.push_back(results[1].str());
-			}
-			break;
-
-		// Function record
-		case 'F':
-			{
-				ParseError result = parseSymbol<Function>(line);
-				if (result)
-				{
-					return result;
-				}
-			}
-			break;
-
-		// Symbol record
-		case 'S':
-			{
-				ParseError result = parseSymbol<Symbol>(line);
-				if (result)
-				{
-					return result;
-				}
-			}
-			break;
-
-		// Type record
-		case 'T':
-			break;
-
-		// Linker record
-		case 'L':
-			if (line.length() < 3 || line[1] != ':')
-			{
-				return INVALID_LINE;
-			}
-			switch (line[2])
-			{
-			// Address of symbol
-			case 'G':
-			case 'F':
-			case 'L':
-			case 'X':
-				{
-					boost::regex regex(
-						"L:X?"
-						"(G|F(.+?)|L(.+?))"  // Scope and location
-						"\\$(.+?)"           // Symbol name
-						"\\$([0-9]+)"        // Level
-						"\\$([0-9]+)"        // Block
-						":([0-9A-Z]+)");     // Address
-					boost::smatch results;
-					if (!regex_match(line, results, regex))
-					{
-						return INVALID_LINE;
-					}
-
-					SymbolAddress symAddr;
-					ParseError result = parseSymbolScope(results[1], results[2], results[3],
-						symAddr.scope, symAddr.scopeDetail);
-					if (result)
-					{
-						return result;
-					}
-
-					symAddr.name = results[4];
-					symAddr.level = lexical_cast<unsigned>(results[5]);
-					symAddr.block = lexical_cast<unsigned>(results[6]);
-
-					istringstream iss(results[7]);
-					iss >> hex >> symAddr.address;
-					assert(iss);
-
-					if (line[2] == 'X')
-					{
-						symbolEndAddresses_.insert(make_pair(symAddr.name, symAddr));
-					}
-					else
-					{
-						symbolAddresses_.insert(make_pair(symAddr.address, symAddr));
-					}
-				}
-				break;
-
-			// ASM line
-			case 'A':
-				break;
-
-			// C line
-			case 'C':
-				{
-					boost::regex regex(
-						"L:C"
-						"\\$(.+?)"        // Filename
-						"\\$([0-9]+)"     // Line number
-						"\\$([0-9]+)"     // Level
-						"\\$([0-9]+)"     // Block
-						":([0-9A-Z]+)");  // End address
-					boost::smatch results;
-					if (!regex_match(line, results, regex))
-					{
-						return INVALID_LINE;
-					}
-
-					CLine line;
-					line.filename = results[1];
-					line.line = lexical_cast<unsigned>(results[2]);
-					line.level = lexical_cast<unsigned>(results[3]);
-					line.block = lexical_cast<unsigned>(results[4]);
-
-					istringstream iss(results[5]);
-					iss >> hex >> line.endAddress;
-					assert(iss);
-
-					cLines_.push_back(line);
-				}
-				break;
-
-			default:
-				return INVALID_LINE;
-			}
-			break;
-
-		default:
-			return BAD_RECORD_TYPE;
-		}
-	}
-
-	if (!f.eof())
-	{
-		return READ_ERROR;
-	}
-
-	valid_ = true;
-	return PARSE_OK;
-}
-
-template <class T>
-CdbFile::ParseError CdbFile::parseSymbol(const string &line)
-{
-	const boost::regex &regex = getSymbolRegex<T>();
-	boost::smatch results;
-	if (!regex_match(line, results, regex))
-	{
-		return INVALID_LINE;
-	}
-
-	T symbol;
-	ParseError result = parseSymbolScope(results[1], results[2], results[3],
-		symbol.scope, symbol.scopeDetail);
-	if (result)
-	{
-		return result;
-	}
-
-	symbol.name = results[4];
-	symbol.level = lexical_cast<unsigned>(results[5]);
-	symbol.block = lexical_cast<unsigned>(results[6]);
-	symbol.type = results[7];
-
-	char addressSpace = results[8].str()[0];
-	switch (addressSpace)
-	{
-	case 'A':
-		symbol.addressSpace = EXTERNAL_STACK;
-		break;
-	case 'B':
-		symbol.addressSpace = INTERNAL_STACK;
-		break;
-	case 'C':
-		symbol.addressSpace = CODE;
-		break;
-	case 'D':
-		symbol.addressSpace = CODE_STATIC_SEGMENT;
-		break;
-	case 'E':
-		symbol.addressSpace = INTERNAL_RAM_LOW;
-		break;
-	case 'F':
-		symbol.addressSpace = EXTERNAL_RAM;
-		break;
-	case 'G':
-		symbol.addressSpace = INTERNAL_RAM;
-		break;
-	case 'H':
-		symbol.addressSpace = BIT_ADDRESSABLE;
-		break;
-	case 'I':
-		symbol.addressSpace = SFR_SPACE;
-		break;
-	case 'J':
-		symbol.addressSpace = SBIT_SPACE;
-		break;
-	case 'R':
-		symbol.addressSpace = REGISTER_SPACE;
-		break;
-	case 'Z':
-		symbol.addressSpace = UNDEFINED_SPACE;
-		break;
-	default:
-		assert(!"all address space types should have been handled");
-		return INVALID_LINE;
-	}
-
-	symbol.onStack = lexical_cast<bool>(results[9]);
-	symbol.stackOffset = lexical_cast<int>(results[10]);
-
-	saveSymbol(symbol, results);
-	return PARSE_OK;
-}
-
-template <class T>
-const boost::regex & CdbFile::getSymbolRegex() const
-{
-	static const boost::regex regex(
-		"S:"
-		"(G|F(.+?)|L(.+?))"  // Scope and location
-		"\\$(.+?)"           // Name
-		"\\$([0-9]+)"        // Level
-		"\\$([0-9]+)"        // Block
-		"\\((.+?)\\)"        // Type
-		",([ABCDEFGHIJRZ])"  // Address space
-		",([01])"            // On stack
-		",(\\-?[0-9]+)"      // Stack offset
-		"(,\\[(.+?)\\])?");  // Registers
-	return regex;
-}
-
-template <>
-const boost::regex & CdbFile::getSymbolRegex<CdbFile::Function>() const
-{
-	static const boost::regex regex(
-		"F:"
-		"(G|F(.+?)|L(.+?))"  // Scope and location
-		"\\$(.+?)"           // Name
-		"\\$([0-9]+)"        // Level
-		"\\$([0-9]+)"        // Block
-		"\\((.+?)\\)"        // Type
-		",([ABCDEFGHIJRZ])"  // Address space
-		",([01])"            // On stack
-		",(\\-?[0-9]+)"      // Stack offset
-		",([01])"            // Is interrupt
-		",([0-9]+)"          // Interrupt number
-		",([0-9]+)");        // Register bank number
-	return regex;
-}
-
-template <class T>
-void CdbFile::saveSymbol(T &symbol, const boost::smatch &results)
-{
-	if (results[12].matched)
-	{
-		string registers = results[12].str();
-		boost::split(symbol.registers, registers, is_any_of(","), token_compress_on);
-	}
-	symbols_.insert(make_pair(symbol.name, symbol));
-}
-
-template <>
-void CdbFile::saveSymbol(Function &symbol, const boost::smatch &results)
-{
-	symbol.isInterrupt = lexical_cast<bool>(results[11]);
-	symbol.interruptNumber = lexical_cast<unsigned>(results[12]);
-	symbol.registerBank = lexical_cast<unsigned>(results[13]);
-	functions_.insert(make_pair(symbol.name, symbol));
-}
-
-CdbFile::ParseError CdbFile::parseSymbolScope(
-	const string &scopeStr,
-	const boost::ssub_match &file,
-	const boost::ssub_match &function,
-	CdbFile::SymbolScope &scope,
-	string &scopeDetail)
-{
-	switch (scopeStr[0])
-	{
-	case 'G':
-		scope = GLOBAL;
-		break;
-
-	case 'F':
-		{
-			if (!file.matched)
-			{
-				return INVALID_LINE;
-			}
-			scope = FILE;
-			scopeDetail = scopeDetail;
-		}
-		break;
-
-	case 'L':
-		{
-			if (!function.matched)
-			{
-				return INVALID_LINE;
-			}
-			scope = FUNCTION;
-			scopeDetail = function;
-		}
-		break;
-
-	default:
-		return INVALID_LINE;
-	}
-
-	return PARSE_OK;
-}
-
-string CdbFile::findFunctionName(uint16_t address)
-{
-	// Find the address in the symbol table
-	map<uint64_t, SymbolAddress>::const_iterator symAddr = symbolAddresses_.find(address);
-	if (symAddr == symbolAddresses_.end())
-	{
-		return string();
-	}
-	else
-	{
-		return symAddr->second.name;
-	}
-}
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <gb_emulator/cdb_file.h>
+
+#include <iomanip>
+#include <sstream>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace fs = boost::filesystem;
+using boost::algorithm::is_any_of;
+using boost::lexical_cast;
+using boost::token_compress_on;
+using std::hex;
+using std::ios_base;
+using std::istream;
+using std::istringstream;
+using std::make_pair;
+using std::map;
+using std::string;
+using std::vector;
+
+istream & getLine(istream &is, string &line)
+{
+	line.clear();
+	char ch;
+	while (is.get(ch))
+	{
+		if (ch == '\n')
+		{
+			break;
+		}
+		else if (ch == '\r')
+		{
+			if (is.peek() == '\n')
+			{
+				is.ignore();
+			}
+			break;
+		}
+		else
+		{
+			line.push_back(ch);
+		}
+	}
+	return is;
+}
+
+CdbFile::CdbFile():
+valid_(false)
+{
+}
+
+CdbFile::ParseError CdbFile::parseFile(const fs::path &path)
+{
+	// Struct containing details from linker start and end address records. This data is merged into
+	// symbols_ in post-processing
+	struct SymbolAddress
+	{
+		SymbolScope scope;
+		std::string scopeDetail;
+		std::string name;
+		unsigned level;
+		unsigned block;
+		uint64_t address;
+	};
+
+	// Clear any existing parsed data
+	modules_.clear();
+	symbols_.clear();
+
+	// Open the file
+	if (!fs::exists(path))
+	{
+		return NO_SUCH_FILE;
+	}
+	fs::ifstream f(path, ios_base::in | ios_base::binary);
+	if (!f)
+	{
+		return OPEN_FAILED;
+	}
+
+	// Temporary data store; this data in this will be merged into symbols_ in post-processing
+	vector<Symbol> functions;
+	vector<SymbolAddress> symbolAddresses;
+	vector<SymbolAddress> symbolEndAddresses;
+	vector<CLine> cLines;
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// Parse the file
+
+	string line;
+	while (getLine(f, line))
+	{
+		// Ignore empty lines
+		if (line.empty())
+		{
+			continue;
+		}
+
+		// Switch on the record type
+		switch (line[0])
+		{
+		// Module record
+		case 'M':
+			{
+				static const boost::regex regex("M:(.+?)");
+				boost::smatch results;
+				if (!regex_match(line, results, regex))
+				{
+					return INVALID_LINE;
+				}
+				modules_.push_back(results[1].str());
+			}
+			break;
+
+		// Function record
+		case 'F':
+			{
+				Symbol function;
+				ParseError result = parseSymbol(line, true, function);
+				if (result)
+				{
+					return result;
+				}
+				functions.push_back(function);
+			}
+			break;
+
+		// Symbol record
+		case 'S':
+			{
+				Symbol symbol;
+				ParseError result = parseSymbol(line, false, symbol);
+				if (result)
+				{
+					return result;
+				}
+				symbols_.insert(make_pair(symbol.name, symbol));
+			}
+			break;
+
+		// Type record
+		case 'T':
+			break;
+
+		// Linker record
+		case 'L':
+			if (line.length() < 3 || line[1] != ':')
+			{
+				return INVALID_LINE;
+			}
+			switch (line[2])
+			{
+			// Address of symbol
+			case 'G':
+			case 'F':
+			case 'L':
+			case 'X':
+				{
+					boost::regex regex(
+						"L:X?"
+						"(G|F(.+?)|L(.+?))"  // Scope and location
+						"\\$(.+?)"           // Symbol name
+						"\\$([0-9]+)"        // Level
+						"\\$([0-9]+)"        // Block
+						":([0-9A-Z]+)");     // Address
+					boost::smatch results;
+					if (!regex_match(line, results, regex))
+					{
+						return INVALID_LINE;
+					}
+
+					SymbolAddress symAddr;
+					ParseError result = parseSymbolScope(results[1], results[2], results[3],
+						symAddr.scope, symAddr.scopeDetail);
+					if (result)
+					{
+						return result;
+					}
+
+					symAddr.name = results[4];
+					symAddr.level = lexical_cast<unsigned>(results[5]);
+					symAddr.block = lexical_cast<unsigned>(results[6]);
+
+					istringstream iss(results[7]);
+					iss >> hex >> symAddr.address;
+					assert(iss);
+
+					if (line[2] == 'X')
+					{
+						symbolEndAddresses.push_back(symAddr);
+					}
+					else
+					{
+						symbolAddresses.push_back(symAddr);
+					}
+				}
+				break;
+
+			// ASM line
+			case 'A':
+				break;
+
+			// C line
+			case 'C':
+				{
+					boost::regex regex(
+						"L:C"
+						"\\$(.+?)"        // Filename
+						"\\$([0-9]+)"     // Line number
+						"\\$([0-9]+)"     // Level
+						"\\$([0-9]+)"     // Block
+						":([0-9A-Z]+)");  // End address
+					boost::smatch results;
+					if (!regex_match(line, results, regex))
+					{
+						return INVALID_LINE;
+					}
+
+					CLine line;
+					line.filename = results[1];
+					line.line = lexical_cast<unsigned>(results[2]);
+					line.level = lexical_cast<unsigned>(results[3]);
+					line.block = lexical_cast<unsigned>(results[4]);
+
+					istringstream iss(results[5]);
+					iss >> hex >> line.endAddress;
+					assert(iss);
+
+					cLines.push_back(line);
+				}
+				break;
+
+			default:
+				return INVALID_LINE;
+			}
+			break;
+
+		default:
+			return BAD_RECORD_TYPE;
+		}
+	}
+
+	if (!f.eof())
+	{
+		return READ_ERROR;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////////////////////
+	// Post processing
+
+	// Merge functions with their associated symbols
+	for (vector<Symbol>::const_iterator function = functions.begin(), end = functions.end();
+		function != end; ++function)
+	{
+		// Find the symbol. There should always be a symbol with the exact same details as the
+		// function
+		map<string, Symbol>::iterator symbol = symbols_.find(function->name);
+		if (symbol == symbols_.end())
+		{
+			return DATA_INCONSISTENCY;
+		}
+
+		// Verify the data matches
+		if (symbol->second.scope        != function->scope ||
+			symbol->second.scopeDetail  != function->scopeDetail  ||
+			symbol->second.type         != function->type ||
+			symbol->second.level        != function->level ||
+			symbol->second.block        != function->block ||
+			symbol->second.onStack      != function->onStack)
+		{
+			return DATA_INCONSISTENCY;
+		}
+
+		// Copy the function data into the symbol
+		symbol->second.functionDataValid = true;
+		symbol->second.stackOffset =       function->stackOffset;
+		symbol->second.isInterrupt =       function->isInterrupt;
+		symbol->second.interruptNumber =   function->interruptNumber;
+		symbol->second.registerBank =      function->registerBank;
+	}
+
+	// Merge symbol start and end addresses
+	for (vector<SymbolAddress>::const_iterator symAddr = symbolAddresses.begin(),
+		end = symbolAddresses.end(); symAddr != end; ++symAddr)
+	{
+		// Find the symbol. Things like string constants get address entries, but they don't have
+		// any associated symbols so they aren't very useful; just throw them away
+		map<string, Symbol>::iterator symbol = symbols_.find(symAddr->name);
+		if (symbol == symbols_.end())
+		{
+			continue;
+		}
+
+		// Verify the data matches
+		if (symbol->second.scope        != symAddr->scope ||
+			symbol->second.scopeDetail  != symAddr->scopeDetail  ||
+			symbol->second.level        != symAddr->level ||
+			symbol->second.block        != symAddr->block)
+		{
+			return DATA_INCONSISTENCY;
+		}
+
+		// Copy the function data into the symbol
+		symbol->second.addressValid = true;
+		symbol->second.address = symAddr->address;
+	}
+
+	for (vector<SymbolAddress>::const_iterator symAddr = symbolEndAddresses.begin(),
+		end = symbolEndAddresses.end(); symAddr != end; ++symAddr)
+	{
+		map<string, Symbol>::iterator symbol = symbols_.find(symAddr->name);
+		if (symbol == symbols_.end())
+		{
+			continue;
+		}
+		if (symbol->second.scope        != symAddr->scope ||
+			symbol->second.scopeDetail  != symAddr->scopeDetail  ||
+			symbol->second.level        != symAddr->level ||
+			symbol->second.block        != symAddr->block)
+		{
+			return DATA_INCONSISTENCY;
+		}
+		symbol->second.endAddressValid = true;
+		symbol->second.endAddress = symAddr->address;
+	}
+
+	// Copy C line entries into their associated symbol
+	for (vector<CLine>::const_iterator line = cLines.begin(), end = cLines.end();
+		line != end; ++line)
+	{
+		Symbol *function = findFunction(line->endAddress);
+		if (!function)
+		{
+			// Source code line outside a function?
+			return DATA_INCONSISTENCY;
+		}
+
+		function->cLines.push_back(*line);
+	}
+
+	valid_ = true;
+	return PARSE_OK;
+}
+
+CdbFile::ParseError CdbFile::parseSymbol(const string &line, bool isFunction, CdbFile::Symbol &symbol)
+{
+	static const boost::regex symbolRegex(
+		"S:"
+		"(G|F(.+?)|L(.+?))"  // Scope and location
+		"\\$(.+?)"           // Name
+		"\\$([0-9]+)"        // Level
+		"\\$([0-9]+)"        // Block
+		"\\((.+?)\\)"        // Type
+		",([ABCDEFGHIJRZ])"  // Address space
+		",([01])"            // On stack
+		",(\\-?[0-9]+)"      // Stack offset
+		"(,\\[(.*?)\\])?");  // Registers
+
+	static const boost::regex functionRegex(
+		"F:"
+		"(G|F(.+?)|L(.+?))"  // Scope and location
+		"\\$(.+?)"           // Name
+		"\\$([0-9]+)"        // Level
+		"\\$([0-9]+)"        // Block
+		"\\((.+?)\\)"        // Type
+		",([ABCDEFGHIJRZ])"  // Address space
+		",([01])"            // On stack
+		",(\\-?[0-9]+)"      // Stack offset
+		",([01])"            // Is interrupt
+		",([0-9]+)"          // Interrupt number
+		",([0-9]+)");        // Register bank number
+
+	const boost::regex &regex = isFunction ? functionRegex : symbolRegex;
+	boost::smatch results;
+	if (!regex_match(line, results, regex))
+	{
+		return INVALID_LINE;
+	}
+
+	ParseError result = parseSymbolScope(results[1], results[2], results[3],
+		symbol.scope, symbol.scopeDetail);
+	if (result)
+	{
+		return result;
+	}
+
+	symbol.name = results[4];
+	symbol.level = lexical_cast<unsigned>(results[5]);
+	symbol.block = lexical_cast<unsigned>(results[6]);
+	symbol.type = results[7];
+
+	char addressSpace = results[8].str()[0];
+	switch (addressSpace)
+	{
+	case 'A':
+		symbol.addressSpace = EXTERNAL_STACK;
+		break;
+	case 'B':
+		symbol.addressSpace = INTERNAL_STACK;
+		break;
+	case 'C':
+		symbol.addressSpace = CODE;
+		break;
+	case 'D':
+		symbol.addressSpace = CODE_STATIC_SEGMENT;
+		break;
+	case 'E':
+		symbol.addressSpace = INTERNAL_RAM_LOW;
+		break;
+	case 'F':
+		symbol.addressSpace = EXTERNAL_RAM;
+		break;
+	case 'G':
+		symbol.addressSpace = INTERNAL_RAM;
+		break;
+	case 'H':
+		symbol.addressSpace = BIT_ADDRESSABLE;
+		break;
+	case 'I':
+		symbol.addressSpace = SFR_SPACE;
+		break;
+	case 'J':
+		symbol.addressSpace = SBIT_SPACE;
+		break;
+	case 'R':
+		symbol.addressSpace = REGISTER_SPACE;
+		break;
+	case 'Z':
+		symbol.addressSpace = UNDEFINED_SPACE;
+		break;
+	default:
+		assert(!"all address space types should have been handled");
+		return INVALID_LINE;
+	}
+
+	symbol.onStack = lexical_cast<bool>(results[9]);
+	symbol.stackOffset = lexical_cast<int>(results[10]);
+
+	symbol.addressValid = false;
+	symbol.endAddressValid = false;
+	symbol.address = 0;
+	symbol.endAddress = 0;
+
+	if (isFunction)
+	{
+		symbol.functionDataValid = true;
+		symbol.isInterrupt = lexical_cast<bool>(results[11]);
+		symbol.interruptNumber = lexical_cast<unsigned>(results[12]);
+		symbol.registerBank = lexical_cast<unsigned>(results[13]);
+	}
+	else
+	{
+		symbol.functionDataValid = false;
+		symbol.isInterrupt = false;
+		symbol.interruptNumber = 0;
+		symbol.registerBank = 0;
+
+		if (results[12].matched)
+		{
+			string registers = results[12].str();
+			boost::split(symbol.registers, registers, is_any_of(","), token_compress_on);
+		}
+	}
+
+	return PARSE_OK;
+}
+
+CdbFile::ParseError CdbFile::parseSymbolScope(
+	const string &scopeStr,
+	const boost::ssub_match &file,
+	const boost::ssub_match &function,
+	CdbFile::SymbolScope &scope,
+	string &scopeDetail)
+{
+	switch (scopeStr[0])
+	{
+	case 'G':
+		scope = GLOBAL;
+		break;
+
+	case 'F':
+		{
+			if (!file.matched)
+			{
+				return INVALID_LINE;
+			}
+			scope = FILE;
+			scopeDetail = scopeDetail;
+		}
+		break;
+
+	case 'L':
+		{
+			if (!function.matched)
+			{
+				return INVALID_LINE;
+			}
+			scope = FUNCTION;
+			scopeDetail = function;
+		}
+		break;
+
+	default:
+		return INVALID_LINE;
+	}
+
+	return PARSE_OK;
+}
+
+CdbFile::Symbol * CdbFile::findFunction(uint64_t address)
+{
+	for (map<string, Symbol>::iterator symbol = symbols_.begin(), end = symbols_.end();
+		symbol != end; ++symbol)
+	{
+		// Only functions will have both the start and end addresses set
+		if (!symbol->second.endAddressValid || !symbol->second.addressValid)
+		{
+			continue;
+		}
+
+		if (address >= symbol->second.address && address <= symbol->second.endAddress)
+		{
+			return &symbol->second;
+		}
+	}
+
+	return NULL;
+}
+
+bool CdbFile::findSource(uint64_t address, CdbFile::SourceLocation &sourceLocation)
+{
+	if (!valid_)
+	{
+		return false;
+	}
+
+	// Find the containing function
+	const Symbol *function = findFunction(address);
+	if (!function)
+	{
+		return false;
+	}
+
+	// Find the associated line of code, if available
+	vector<CLine>::const_iterator theLine = function->cLines.end();
+	for (vector<CLine>::const_iterator line = function->cLines.begin(),
+		end = function->cLines.end(); line != end; ++line)
+	{
+		if (line->endAddress > address)
+		{
+			break;
+		}
+
+		theLine = line;
+	}
+
+	// Populate the output data
+	sourceLocation.function = function->name;
+	sourceLocation.functionOffset = address - function->address;
+	if (theLine != function->cLines.end())
+	{
+		uint64_t lineBegin;
+		if (theLine == function->cLines.begin())
+		{
+			lineBegin = function->address;
+		}
+		else
+		{
+			lineBegin = (theLine - 1)->endAddress + 1;
+		}
+		assert(lineBegin <= address);
+
+		sourceLocation.sourceFile = theLine->filename;
+		sourceLocation.language = SourceLocation::C;
+		sourceLocation.line = theLine->line;
+		sourceLocation.lineOffset = address - lineBegin;
+	}
+	else
+	{
+		sourceLocation.sourceFile.clear();
+		sourceLocation.language = SourceLocation::ASSEMBLY;
+		sourceLocation.line = 0;
+		sourceLocation.lineOffset = 0;
+	}
+
+	return true;
+}

File gb_emulator/src/gb_debugger.cpp

 
 	// Add a single stack frame representing the BIOS entry point
 	GbStackFrame frame;
+
+	frame.isRom = false;
 	frame.module = make_pair(gb_.biosFilename_, static_cast<uint32_t>(gb_.bios_.size()));
-	frame.function = "_biosEntry";
 	frame.stackAddrMin = 0xfffe;
 	frame.stackAddrMax = 0xfffe;
 	frame.functionAddr = 0;
 		stack_.clear();
 
 		GbStackFrame frame;
+		frame.isRom = true;
 		frame.module = make_pair(gb_.romFilename_, static_cast<uint32_t>(gb_.rom_.size()));
-		frame.function = "_romEntry";
 		frame.stackAddrMin = gb_.cpu_.sp;
 		frame.stackAddrMax = gb_.cpu_.sp;
 		frame.functionAddr = 0x100;
 				stack_.clear();
 
 				GbStackFrame frame;
-				frame.module = getModule();
+				frame.module = getModule(&frame.isRom);
 				frame.stackAddrMin = address;
 				frame.stackAddrMax = address;
 				frame.functionAddr = gb_.cpu_.pc;
 	// Create a new stack frame
 	{
 		GbStackFrame frame;
-		frame.module = getModule();
+		frame.module = getModule(&frame.isRom);
 		frame.stackAddrMin = gb_.cpu_.sp - 2;
 		frame.stackAddrMax = gb_.cpu_.sp - 1;
 		frame.returnAddr = gb_.cpu_.pc;
 
 		if (interrupts & VBLANK_INTR)
 		{
-			frame.function = "_intVblank";
 			frame.functionAddr = frame.instructionAddr = VBLANK_INTR_ADDR;
 		}
 		else if (interrupts & LCD_STAT_INTR)
 		{
-			frame.function = "_intLcdStat";
 			frame.functionAddr = frame.instructionAddr = LCD_STAT_INTR_ADDR;
 		}
 		else if (interrupts & TIMER_INTR)
 		{
-			frame.function = "_intTimer";
 			frame.functionAddr = frame.instructionAddr = TIMER_INTR_ADDR;
 		}
 		else if (interrupts & SERIAL_INTR)
 		{
-			frame.function = "_intSerial";
 			frame.functionAddr = frame.instructionAddr = SERIAL_INTR_ADDR;
 		}
 		else if (interrupts & JOYPAD_INTR)
 		{
-			frame.function = "_intJoypad";
 			frame.functionAddr = frame.instructionAddr = JOYPAD_INTR_ADDR;
 		}
 		else
 
 		// Create a new stack frame
 		{
-			bool isRom;
 			GbStackFrame frame;
-			frame.module = getModule(&isRom);
+			frame.module = getModule(&frame.isRom);
 			frame.stackAddrMin = gb_.cpu_.sp - 2;
 			frame.stackAddrMax = gb_.cpu_.sp - 1;
 			frame.functionAddr = frame.instructionAddr = gb_.mem_.read16(gb_.cpu_.pc + 1);
 			frame.returnAddr = gb_.cpu_.pc + 3;
 
-			if (isRom)
-			{
-				frame.function = romSymbols_.findFunctionName(frame.functionAddr);
-			}
-
 			stack_.push_back(frame);
 		}
 	}
 	frame.stackAddrMin = gb_.cpu_.sp;
 	frame.instructionAddr = gb_.cpu_.pc;
 
+	// Get the source information for the stack frames
+	for (deque<GbStackFrame>::iterator frame = stack_.begin(), end = stack_.end();
+		frame != end; ++frame)
+	{
+		if (frame->isRom)
+		{
+			frame->hasDebugInfo =
+				romSymbols_.findSource(frame->instructionAddr, frame->sourceLocation);
+		}
+		else
+		{
+			frame->hasDebugInfo = false;
+			frame->sourceLocation.function.clear();
+			frame->sourceLocation.functionOffset = 0;
+			frame->sourceLocation.sourceFile.clear();
+			frame->sourceLocation.language = CdbFile::SourceLocation::ASSEMBLY;
+			frame->sourceLocation.line = 0;
+			frame->sourceLocation.lineOffset = 0;
+		}
+	}
+
 	NOTIFY(stackFrames, stack_)
 }