diff options
-rw-r--r-- | src/dlls/mscoree/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/dlls/mscoree/unixinterface.cpp | 26 | ||||
-rw-r--r-- | src/vm/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/vm/gdbjit.cpp | 1003 | ||||
-rw-r--r-- | src/vm/gdbjit.h | 107 | ||||
-rw-r--r-- | src/vm/gdbjithelpers.h | 31 | ||||
-rw-r--r-- | src/vm/util.cpp | 17 |
7 files changed, 1194 insertions, 2 deletions
diff --git a/src/dlls/mscoree/CMakeLists.txt b/src/dlls/mscoree/CMakeLists.txt index 7af76fbe6b..6a157e4105 100644 --- a/src/dlls/mscoree/CMakeLists.txt +++ b/src/dlls/mscoree/CMakeLists.txt @@ -1,5 +1,9 @@ include_directories("../../inc") +if(FEATURE_GDBJIT) + add_definitions(-DFEATURE_GDBJIT) +endif(FEATURE_GDBJIT) + set(CLR_SOURCES mscoree.cpp unixinterface.cpp diff --git a/src/dlls/mscoree/unixinterface.cpp b/src/dlls/mscoree/unixinterface.cpp index be345b3ca1..897924c90c 100644 --- a/src/dlls/mscoree/unixinterface.cpp +++ b/src/dlls/mscoree/unixinterface.cpp @@ -15,6 +15,9 @@ #include <utilcode.h> #include <corhost.h> #include <configuration.h> +#ifdef FEATURE_GDBJIT +#include "../../vm/gdbjithelpers.h" +#endif // FEATURE_GDBJIT typedef int (STDMETHODCALLTYPE *HostMain)( const int argc, @@ -137,6 +140,11 @@ static void ConvertConfigPropertiesToUnicode( extern "C" LPCWSTR g_CLRJITPath; #endif // !defined(FEATURE_MERGE_JIT_AND_ENGINE) +#ifdef FEATURE_GDBJIT +GetInfoForMethodDelegate getInfoForMethodDelegate = NULL; +extern "C" int coreclr_create_delegate(void*, unsigned int, const char*, const char*, const char*, void**); +#endif //FEATURE_GDBJIT + // // Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain // @@ -238,8 +246,24 @@ int coreclr_initialize( { host.SuppressRelease(); *hostHandle = host; - } +#ifdef FEATURE_GDBJIT + hr = coreclr_create_delegate(*hostHandle, + *domainId, + "System.Diagnostics.Debug.SymbolReader", + "System.Diagnostics.Debug.SymbolReader.SymbolReader", + "GetInfoForMethod", + (void**)&getInfoForMethodDelegate); + + if (!SUCCEEDED(hr)) + { + fprintf(stderr, + "Can't create delegate for 'System.Diagnostics.Debug.SymbolReader.SymbolReader.GetInfoForMethod' " + "method - status: 0x%08x\n", hr); + } + hr = S_OK; // We don't need to fail if we can't create delegate +#endif + } return hr; } diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 89a6437da1..66fc6432af 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -27,6 +27,13 @@ if(CLR_CMAKE_PLATFORM_UNIX) add_compile_options(-fPIC) endif(CLR_CMAKE_PLATFORM_UNIX) +if(FEATURE_GDBJIT) + set(VM_SOURCES_GDBJIT + gdbjit.cpp + ) + add_definitions(-DFEATURE_GDBJIT) +endif(FEATURE_GDBJIT) + set(VM_SOURCES_DAC_AND_WKS_COMMON appdomain.cpp array.cpp @@ -109,6 +116,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON virtualcallstub.cpp win32threadpool.cpp zapsig.cpp + ${VM_SOURCES_GDBJIT} ) if(FEATURE_READYTORUN) diff --git a/src/vm/gdbjit.cpp b/src/vm/gdbjit.cpp new file mode 100644 index 0000000000..435dec4da3 --- /dev/null +++ b/src/vm/gdbjit.cpp @@ -0,0 +1,1003 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: gdbjit.cpp +// + +// +// NotifyGdb implementation. +// +//***************************************************************************** + +#include "common.h" +#include "gdbjit.h" +#include "gdbjithelpers.h" + +struct DebuggerILToNativeMap +{ + ULONG ilOffset; + ULONG nativeStartOffset; + ULONG nativeEndOffset; + ICorDebugInfo::SourceTypes source; +}; +BYTE* DebugInfoStoreNew(void * pData, size_t cBytes) +{ + return new (nothrow) BYTE[cBytes]; +} + +/* Get IL to native offsets map */ +HRESULT +GetMethodNativeMap(MethodDesc* methodDesc, + ULONG32* numMap, + DebuggerILToNativeMap** map) +{ + // Use the DebugInfoStore to get IL->Native maps. + // It doesn't matter whether we're jitted, ngenned etc. + + DebugInfoRequest request; + TADDR nativeCodeStartAddr = PCODEToPINSTR(methodDesc->GetNativeCode()); + request.InitFromStartingAddr(methodDesc, nativeCodeStartAddr); + + // Bounds info. + ULONG32 countMapCopy; + NewHolder<ICorDebugInfo::OffsetMapping> mapCopy(NULL); + + BOOL success = DebugInfoManager::GetBoundariesAndVars(request, + DebugInfoStoreNew, + NULL, // allocator + &countMapCopy, + &mapCopy, + NULL, + NULL); + + if (!success) + { + return E_FAIL; + } + + // Need to convert map formats. + *numMap = countMapCopy; + + *map = new (nothrow) DebuggerILToNativeMap[countMapCopy]; + if (!*map) + { + return E_OUTOFMEMORY; + } + + ULONG32 i; + for (i = 0; i < *numMap; i++) + { + (*map)[i].ilOffset = mapCopy[i].ilOffset; + (*map)[i].nativeStartOffset = mapCopy[i].nativeOffset; + if (i > 0) + { + (*map)[i - 1].nativeEndOffset = (*map)[i].nativeStartOffset; + } + (*map)[i].source = mapCopy[i].source; + } + if (*numMap >= 1) + { + (*map)[i - 1].nativeEndOffset = 0; + } + return S_OK; +} + +/* Get mapping of IL offsets to source line numbers */ +HRESULT +GetDebugInfoFromPDB(MethodDesc* MethodDescPtr, SymbolsInfo** symInfo, unsigned int &symInfoLen) +{ + DebuggerILToNativeMap* map = NULL; + + ULONG32 numMap; + + if (!getInfoForMethodDelegate) + return E_FAIL; + + if (GetMethodNativeMap(MethodDescPtr, &numMap, &map) != S_OK) + return E_FAIL; + + const Module* mod = MethodDescPtr->GetMethodTable()->GetModule(); + SString modName = mod->GetFile()->GetPath(); + StackScratchBuffer scratch; + const char* szModName = modName.GetUTF8(scratch); + + MethodDebugInfo* methodDebugInfo = new (nothrow) MethodDebugInfo(); + if (methodDebugInfo == nullptr) + return E_OUTOFMEMORY; + methodDebugInfo->points = (SequencePointInfo*) CoTaskMemAlloc(sizeof(SequencePointInfo) * numMap); + if (methodDebugInfo->points == nullptr) + return E_OUTOFMEMORY; + methodDebugInfo->size = numMap; + + if (!getInfoForMethodDelegate(szModName, MethodDescPtr->GetMemberDef(), *methodDebugInfo)) + return E_FAIL; + + symInfoLen = methodDebugInfo->size; + *symInfo = new (nothrow) SymbolsInfo[symInfoLen]; + if (*symInfo == nullptr) + return E_FAIL; + + for (ULONG32 i = 0; i < symInfoLen; i++) + { + for (ULONG32 j = 0; j < numMap; j++) + { + if (methodDebugInfo->points[i].ilOffset == map[j].ilOffset) + { + SymbolsInfo& s = (*symInfo)[i]; + const SequencePointInfo& sp = methodDebugInfo->points[i]; + + s.nativeOffset = map[j].nativeStartOffset; + s.ilOffset = map[j].ilOffset; + s.fileIndex = 0; + //wcscpy(s.fileName, sp.fileName); + int len = WideCharToMultiByte(CP_UTF8, 0, sp.fileName, -1, s.fileName, sizeof(s.fileName), NULL, NULL); + s.fileName[len] = 0; + s.lineNumber = sp.lineNumber; + } + } + } + + CoTaskMemFree(methodDebugInfo->points); + return S_OK; +} + +// GDB JIT interface +typedef enum +{ + JIT_NOACTION = 0, + JIT_REGISTER_FN, + JIT_UNREGISTER_FN +} jit_actions_t; + +struct jit_code_entry +{ + struct jit_code_entry *next_entry; + struct jit_code_entry *prev_entry; + const char *symfile_addr; + UINT64 symfile_size; +}; + +struct jit_descriptor +{ + UINT32 version; + /* This type should be jit_actions_t, but we use uint32_t + to be explicit about the bitwidth. */ + UINT32 action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; +}; +// GDB puts a breakpoint in this function. +// To prevent from inlining we add noinline attribute and inline assembler statement. +extern "C" +void __attribute__((noinline)) __jit_debug_register_code() { __asm__(""); }; + +/* Make sure to specify the version statically, because the + debugger may check the version before we can set it. */ +struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 }; + +// END of GDB JIT interface + +/* Predefined section names */ +const char* SectionNames[] = { + "", ".text", ".shstrtab", ".debug_str", ".debug_abbrev", ".debug_info", + ".debug_pubnames", ".debug_pubtypes", ".debug_line", "" +}; + +const int SectionNamesCount = sizeof(SectionNames) / sizeof(SectionNames[0]); + +/* Static data for section headers */ +struct SectionHeader { + uint32_t m_type; + uint64_t m_flags; +} Sections[] = { + {SHT_NULL, 0}, + {SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR}, + {SHT_STRTAB, 0}, + {SHT_PROGBITS, SHF_MERGE | SHF_STRINGS }, + {SHT_PROGBITS, 0}, + {SHT_PROGBITS, 0}, + {SHT_PROGBITS, 0}, + {SHT_PROGBITS, 0}, + {SHT_PROGBITS, 0} +}; + +/* Static data for .debug_str section */ +const char* DebugStrings[] = { + "CoreCLR", "" /* module name */, "" /* module path */, "" /* method name */, "int" +}; + +const int DebugStringCount = sizeof(DebugStrings) / sizeof(DebugStrings[0]); + +/* Static data for .debug_abbrev */ +const unsigned char AbbrevTable[] = { + 1, DW_TAG_compile_unit, DW_CHILDREN_yes, + DW_AT_producer, DW_FORM_strp, DW_AT_language, DW_FORM_data2, DW_AT_name, DW_FORM_strp, + DW_AT_stmt_list, DW_FORM_sec_offset, 0, 0, + 2, DW_TAG_subprogram, DW_CHILDREN_no, + DW_AT_name, DW_FORM_strp, DW_AT_decl_file, DW_FORM_data1, DW_AT_decl_line, DW_FORM_data1, + DW_AT_type, DW_FORM_ref4, DW_AT_external, DW_FORM_flag_present, 0, 0, + 3, DW_TAG_base_type, DW_CHILDREN_no, + DW_AT_name, DW_FORM_strp, DW_AT_encoding, DW_FORM_data1, DW_AT_byte_size, DW_FORM_data1,0, 0, + 0 +}; + +const int AbbrevTableSize = sizeof(AbbrevTable); + +/* Static data for .debug_line, including header */ +#define DWARF_LINE_BASE (-5) +#define DWARF_LINE_RANGE 14 +#define DWARF_OPCODE_BASE 13 + +DwarfLineNumHeader LineNumHeader = { + 0, 2, 0, 1, 1, DWARF_LINE_BASE, DWARF_LINE_RANGE, DWARF_OPCODE_BASE, {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1} +}; + +/* Static data for .debug_info */ +struct __attribute__((packed)) DebugInfo +{ + uint8_t m_cu_abbrev; + uint32_t m_prod_off; + uint16_t m_lang; + uint32_t m_cu_name; + uint32_t m_line_num; + + uint8_t m_sub_abbrev; + uint32_t m_sub_name; + uint8_t m_file, m_line; + uint32_t m_sub_type; + + uint8_t m_type_abbrev; + uint32_t m_type_name; + uint8_t m_encoding; + uint8_t m_byte_size; +} debugInfo = { + 1, 0, DW_LANG_C89, 0, 0, + 2, 0, 1, 1, 37, + 3, 0, DW_ATE_signed, 4 +}; + +/* Create ELF/DWARF debug info for jitted method */ +void NotifyGdb::MethodCompiled(MethodDesc* MethodDescPtr) +{ + PCODE pCode = MethodDescPtr->GetNativeCode(); + + if (pCode == NULL) + return; + unsigned int symInfoLen = 0; + NewArrayHolder<SymbolsInfo> symInfo = nullptr; + + /* Get method name & size of jitted code */ + LPCUTF8 methodName = MethodDescPtr->GetName(); + EECodeInfo codeInfo(pCode); + TADDR codeSize = codeInfo.GetCodeManager()->GetFunctionSize(codeInfo.GetGCInfoToken()); + +#ifdef _TARGET_ARM_ + pCode &= ~1; // clear thumb flag for debug info +#endif + + /* Get module name */ + const Module* mod = MethodDescPtr->GetMethodTable()->GetModule(); + SString modName = mod->GetFile()->GetPath(); + StackScratchBuffer scratch; + const char* szModName = modName.GetUTF8(scratch); + const char *szModulePath, *szModuleFile; + + SplitPathname(szModName, szModulePath, szModuleFile); + + /* Get debug info for method from portable PDB */ + HRESULT hr = GetDebugInfoFromPDB(MethodDescPtr, &symInfo, symInfoLen); + if (FAILED(hr) || symInfoLen == 0) + { + return; + } + + MemBuf elfHeader, sectHeaders, sectStr, dbgInfo, dbgAbbrev, dbgPubname, dbgPubType, dbgLine, dbgStr, elfFile; + + /* Build .debug_abbrev section */ + if (!BuildDebugAbbrev(dbgAbbrev)) + { + return; + } + + /* Build .debug_line section */ + if (!BuildLineTable(dbgLine, pCode, symInfo, symInfoLen)) + { + return; + } + + DebugStrings[1] = szModuleFile; + DebugStrings[3] = methodName; + + /* Build .debug_str section */ + if (!BuildDebugStrings(dbgStr)) + { + return; + } + + /* Build .debug_info section */ + if (!BuildDebugInfo(dbgInfo)) + { + return; + } + + /* Build .debug_pubname section */ + if (!BuildDebugPub(dbgPubname, methodName, dbgInfo.MemSize, 26)) + { + return; + } + + /* Build debug_pubtype section */ + if (!BuildDebugPub(dbgPubType, "int", dbgInfo.MemSize, 37)) + { + return; + } + + /* Build section names section */ + if (!BuildSectionNameTable(sectStr)) + { + return; + } + + /* Build section headers table */ + if (!BuildSectionTable(sectHeaders)) + { + return; + } + + /* Patch section offsets & sizes */ + long offset = sizeof(Elf_Ehdr); + Elf_Shdr* pShdr = reinterpret_cast<Elf_Shdr*>(sectHeaders.MemPtr.GetValue()); + ++pShdr; // .text + pShdr->sh_addr = pCode; + pShdr->sh_size = codeSize; + ++pShdr; // .shstrtab + pShdr->sh_offset = offset; + pShdr->sh_size = sectStr.MemSize; + offset += sectStr.MemSize; + ++pShdr; // .debug_str + pShdr->sh_offset = offset; + pShdr->sh_size = dbgStr.MemSize; + offset += dbgStr.MemSize; + ++pShdr; // .debug_abbrev + pShdr->sh_offset = offset; + pShdr->sh_size = dbgAbbrev.MemSize; + offset += dbgAbbrev.MemSize; + ++pShdr; // .debug_info + pShdr->sh_offset = offset; + pShdr->sh_size = dbgInfo.MemSize; + offset += dbgInfo.MemSize; + ++pShdr; // .debug_pubnames + pShdr->sh_offset = offset; + pShdr->sh_size = dbgPubname.MemSize; + offset += dbgPubname.MemSize; + ++pShdr; // .debug_pubtypes + pShdr->sh_offset = offset; + pShdr->sh_size = dbgPubType.MemSize; + offset += dbgPubType.MemSize; + ++pShdr; // .debug_line + pShdr->sh_offset = offset; + pShdr->sh_size = dbgLine.MemSize; + offset += dbgLine.MemSize; + + /* Build ELF header */ + if (!BuildELFHeader(elfHeader)) + { + return; + } + Elf_Ehdr* header = reinterpret_cast<Elf_Ehdr*>(elfHeader.MemPtr.GetValue()); +#ifdef _TARGET_ARM_ + header->e_flags = EF_ARM_EABI_VER5; +#ifdef ARM_SOFTFP + header->e_flags |= EF_ARM_SOFT_FLOAT; +#else + header->e_flags |= EF_ARM_VFP_FLOAT; +#endif +#endif + header->e_shoff = offset; + header->e_shentsize = sizeof(Elf_Shdr); + header->e_shnum = SectionNamesCount - 1; + header->e_shstrndx = 2; + + /* Build ELF image in memory */ + elfFile.MemSize = elfHeader.MemSize + sectStr.MemSize + dbgStr.MemSize + dbgAbbrev.MemSize + + dbgInfo.MemSize + dbgPubname.MemSize + dbgPubType.MemSize + dbgLine.MemSize + sectHeaders.MemSize; + elfFile.MemPtr = new (nothrow) char[elfFile.MemSize]; + if (elfFile.MemPtr == nullptr) + { + return; + } + + /* Copy section data */ + offset = 0; + memcpy(elfFile.MemPtr, elfHeader.MemPtr, elfHeader.MemSize); + offset += elfHeader.MemSize; + memcpy(elfFile.MemPtr + offset, sectStr.MemPtr, sectStr.MemSize); + offset += sectStr.MemSize; + memcpy(elfFile.MemPtr + offset, dbgStr.MemPtr, dbgStr.MemSize); + offset += dbgStr.MemSize; + memcpy(elfFile.MemPtr + offset, dbgAbbrev.MemPtr, dbgAbbrev.MemSize); + offset += dbgAbbrev.MemSize; + memcpy(elfFile.MemPtr + offset, dbgInfo.MemPtr, dbgInfo.MemSize); + offset += dbgInfo.MemSize; + memcpy(elfFile.MemPtr + offset, dbgPubname.MemPtr, dbgPubname.MemSize); + offset += dbgPubname.MemSize; + memcpy(elfFile.MemPtr + offset, dbgPubType.MemPtr, dbgPubType.MemSize); + offset += dbgPubType.MemSize; + memcpy(elfFile.MemPtr + offset, dbgLine.MemPtr, dbgLine.MemSize); + offset += dbgLine.MemSize; + memcpy(elfFile.MemPtr + offset, sectHeaders.MemPtr, sectHeaders.MemSize); + + /* Create GDB JIT structures */ + jit_code_entry* jit_symbols = new (nothrow) jit_code_entry; + + if (jit_symbols == nullptr) + { + return; + } + + /* Fill the new entry */ + jit_symbols->next_entry = jit_symbols->prev_entry = 0; + jit_symbols->symfile_addr = elfFile.MemPtr; + jit_symbols->symfile_size = elfFile.MemSize; + + /* Link into list */ + jit_code_entry *head = __jit_debug_descriptor.first_entry; + __jit_debug_descriptor.first_entry = jit_symbols; + if (head != 0) + { + jit_symbols->next_entry = head; + head->prev_entry = jit_symbols; + } + + /* Notify the debugger */ + __jit_debug_descriptor.relevant_entry = jit_symbols; + __jit_debug_descriptor.action_flag = JIT_REGISTER_FN; + __jit_debug_register_code(); + +} + +void NotifyGdb::MethodDropped(MethodDesc* MethodDescPtr) +{ + PCODE pCode = MethodDescPtr->GetNativeCode(); + + if (pCode == NULL) + return; + + /* Find relevant entry */ + for (jit_code_entry* jit_symbols = __jit_debug_descriptor.first_entry; jit_symbols != 0; jit_symbols = jit_symbols->next_entry) + { + const char* ptr = jit_symbols->symfile_addr; + uint64_t size = jit_symbols->symfile_size; + + const Elf_Ehdr* pEhdr = reinterpret_cast<const Elf_Ehdr*>(ptr); + const Elf_Shdr* pShdr = reinterpret_cast<const Elf_Shdr*>(ptr + pEhdr->e_shoff); + ++pShdr; // bump to .text section + if (pShdr->sh_addr == pCode) + { + /* Notify the debugger */ + __jit_debug_descriptor.relevant_entry = jit_symbols; + __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN; + __jit_debug_register_code(); + + /* Free memory */ + delete[] ptr; + + /* Unlink from list */ + if (jit_symbols->prev_entry == 0) + __jit_debug_descriptor.first_entry = jit_symbols->next_entry; + else + jit_symbols->prev_entry->next_entry = jit_symbols->next_entry; + delete jit_symbols; + break; + } + } +} + +/* Build the DWARF .debug_line section */ +bool NotifyGdb::BuildLineTable(MemBuf& buf, PCODE startAddr, SymbolsInfo* lines, unsigned nlines) +{ + MemBuf fileTable, lineProg; + + /* Build file table */ + if (!BuildFileTable(fileTable, lines, nlines)) + return false; + /* Build line info program */ + if (!BuildLineProg(lineProg, startAddr, lines, nlines)) + { + return false; + } + + buf.MemSize = sizeof(DwarfLineNumHeader) + 1 + fileTable.MemSize + lineProg.MemSize; + buf.MemPtr = new (nothrow) char[buf.MemSize]; + + if (buf.MemPtr == nullptr) + { + return false; + } + + /* Fill the line info header */ + DwarfLineNumHeader* header = reinterpret_cast<DwarfLineNumHeader*>(buf.MemPtr.GetValue()); + memcpy(buf.MemPtr, &LineNumHeader, sizeof(DwarfLineNumHeader)); + header->m_length = buf.MemSize - sizeof(uint32_t); + header->m_hdr_length = sizeof(DwarfLineNumHeader) + 1 + fileTable.MemSize - 2 * sizeof(uint32_t) - sizeof(uint16_t); + buf.MemPtr[sizeof(DwarfLineNumHeader)] = 0; // this is for missing directory table + /* copy file table */ + memcpy(buf.MemPtr + sizeof(DwarfLineNumHeader) + 1, fileTable.MemPtr, fileTable.MemSize); + /* copy line program */ + memcpy(buf.MemPtr + sizeof(DwarfLineNumHeader) + 1 + fileTable.MemSize, lineProg.MemPtr, lineProg.MemSize); + + return true; +} + +/* Buid the source files table for DWARF source line info */ +bool NotifyGdb::BuildFileTable(MemBuf& buf, SymbolsInfo* lines, unsigned nlines) +{ + const char** files = nullptr; + unsigned nfiles = 0; + + /* GetValue file names and replace them with indices in file table */ + files = new (nothrow) const char*[nlines]; + if (files == nullptr) + return false; + for (unsigned i = 0; i < nlines; ++i) + { + const char *filePath, *fileName; + SplitPathname(lines[i].fileName, filePath, fileName); + + /* if this isn't first then we already added file, so adjust index */ + lines[i].fileIndex = (nfiles) ? (nfiles - 1) : (nfiles); + + bool found = false; + for (int j = 0; j < nfiles; ++j) + { + if (strcmp(fileName, files[j]) == 0) + { + found = true; + break; + } + } + + /* add new source file */ + if (!found) + { + files[nfiles++] = fileName; + } + } + + /* build file table */ + unsigned totalSize = 0; + + for (unsigned i = 0; i < nfiles; ++i) + { + totalSize += strlen(files[i]) + 1 + 3; + } + totalSize += 1; + + buf.MemSize = totalSize; + buf.MemPtr = new (nothrow) char[buf.MemSize]; + + if (buf.MemPtr == nullptr) + { + delete[] files; + return false; + } + + /* copy collected file names */ + char *ptr = buf.MemPtr; + for (unsigned i = 0; i < nfiles; ++i) + { + strcpy(ptr, files[i]); + ptr += strlen(files[i]) + 1; + // three LEB128 entries which we don't care + *ptr++ = 0; + *ptr++ = 0; + *ptr++ = 0; + } + // final zero byte + *ptr = 0; + + delete[] files; + return true; +} + +/* Command to set absolute address */ +void NotifyGdb::IssueSetAddress(char*& ptr, PCODE addr) +{ + *ptr++ = 0; + *ptr++ = ADDRESS_SIZE + 1; + *ptr++ = DW_LNE_set_address; + *reinterpret_cast<PCODE*>(ptr) = addr; + ptr += ADDRESS_SIZE; +} + +/* End of line program */ +void NotifyGdb::IssueEndOfSequence(char*& ptr) +{ + *ptr++ = 0; + *ptr++ = 1; + *ptr++ = DW_LNE_end_sequence; +} + +/* Command w/o parameters */ +void NotifyGdb::IssueSimpleCommand(char*& ptr, uint8_t command) +{ + *ptr++ = command; +} + +/* Command with one LEB128 parameter */ +void NotifyGdb::IssueParamCommand(char*& ptr, uint8_t command, char* param, int param_size) +{ + *ptr++ = command; + while (param_size-- > 0) + { + *ptr++ = *param++; + } +} + +/* Special command moves address, line number and issue one row to source line matrix */ +void NotifyGdb::IssueSpecialCommand(char*& ptr, int8_t line_shift, uint8_t addr_shift) +{ + *ptr++ = (line_shift - DWARF_LINE_BASE) + addr_shift * DWARF_LINE_RANGE + DWARF_OPCODE_BASE; +} + +/* Check to see if given shifts are fit into one byte command */ +bool NotifyGdb::FitIntoSpecialOpcode(int8_t line_shift, uint8_t addr_shift) +{ + unsigned opcode = (line_shift - DWARF_LINE_BASE) + addr_shift * DWARF_LINE_RANGE + DWARF_OPCODE_BASE; + + return opcode < 255; +} + +/* Build program for DWARF source line section */ +bool NotifyGdb::BuildLineProg(MemBuf& buf, PCODE startAddr, SymbolsInfo* lines, unsigned nlines) +{ + static char cnv_buf[16]; + + /* reserve memory assuming worst case: one extended and one special command for each line */ + buf.MemSize = nlines * ( 4 + ADDRESS_SIZE) + 4; + buf.MemPtr = new (nothrow) char[buf.MemSize]; + char* ptr = buf.MemPtr; + + if (buf.MemPtr == nullptr) + return false; + + /* set absolute start address */ + IssueSetAddress(ptr, startAddr); + IssueSimpleCommand(ptr, DW_LNS_set_prologue_end); + + int prevLine = 1, prevAddr = 0, prevFile = 0; + + for (int i = 0; i < nlines; ++i) + { + /* different source file */ + if (lines[i].fileIndex != prevFile) + { + int len = Leb128Encode(static_cast<uint32_t>(lines[i].fileIndex+1), cnv_buf, sizeof(cnv_buf)); + IssueParamCommand(ptr, DW_LNS_set_file, cnv_buf, len); + prevFile = lines[i].fileIndex; + } + /* too big line number shift */ + if (lines[i].lineNumber - prevLine > (DWARF_LINE_BASE + DWARF_LINE_RANGE - 1)) + { + int len = Leb128Encode(static_cast<int32_t>(lines[i].lineNumber - prevLine), cnv_buf, sizeof(cnv_buf)); + IssueParamCommand(ptr, DW_LNS_advance_line, cnv_buf, len); + prevLine = lines[i].lineNumber; + } + /* first try special opcode */ + if (FitIntoSpecialOpcode(lines[i].lineNumber - prevLine, lines[i].nativeOffset - prevAddr)) + IssueSpecialCommand(ptr, lines[i].lineNumber - prevLine, lines[i].nativeOffset - prevAddr); + else + { + IssueSetAddress(ptr, startAddr + lines[i].nativeOffset); + IssueSpecialCommand(ptr, lines[i].lineNumber - prevLine, 0); + } + + prevLine = lines[i].lineNumber; + prevAddr = lines[i].nativeOffset; + } + + IssueEndOfSequence(ptr); + + buf.MemSize = ptr - buf.MemPtr; + return true; +} + +/* Build the DWARF .debug_str section */ +bool NotifyGdb::BuildDebugStrings(MemBuf& buf) +{ + uint32_t totalLength = 0; + + /* calculate total section size */ + for (int i = 0; i < DebugStringCount; ++i) + { + totalLength += strlen(DebugStrings[i]) + 1; + } + + buf.MemSize = totalLength; + buf.MemPtr = new (nothrow) char[totalLength]; + + if (buf.MemPtr == nullptr) + return false; + + /* copy strings */ + char* bufPtr = buf.MemPtr; + for (int i = 0; i < DebugStringCount; ++i) + { + strcpy(bufPtr, DebugStrings[i]); + bufPtr += strlen(DebugStrings[i]) + 1; + } + + return true; +} + +/* Build the DWARF .debug_abbrev section */ +bool NotifyGdb::BuildDebugAbbrev(MemBuf& buf) +{ + buf.MemPtr = new (nothrow) char[AbbrevTableSize]; + buf.MemSize = AbbrevTableSize; + + if (buf.MemPtr == nullptr) + return false; + + memcpy(buf.MemPtr, AbbrevTable, AbbrevTableSize); + return true; +} + +/* Build tge DWARF .debug_info section */ +bool NotifyGdb::BuildDebugInfo(MemBuf& buf) +{ + buf.MemSize = sizeof(DwarfCompUnit) + sizeof(DebugInfo) + 1; + buf.MemPtr = new (nothrow) char[buf.MemSize]; + + if (buf.MemPtr == nullptr) + return false; + + /* Compile uint header */ + DwarfCompUnit* cu = reinterpret_cast<DwarfCompUnit*>(buf.MemPtr.GetValue()); + cu->m_length = buf.MemSize - sizeof(uint32_t); + cu->m_version = 4; + cu->m_abbrev_offset = 0; + cu->m_addr_size = ADDRESS_SIZE; + + /* copy debug information */ + DebugInfo* di = reinterpret_cast<DebugInfo*>(buf.MemPtr + sizeof(DwarfCompUnit)); + memcpy(buf.MemPtr + sizeof(DwarfCompUnit), &debugInfo, sizeof(DebugInfo)); + di->m_prod_off = 0; + di->m_cu_name = strlen(DebugStrings[0]) + 1; + di->m_sub_name = strlen(DebugStrings[0]) + 1 + strlen(DebugStrings[1]) + 1 + strlen(DebugStrings[2]) + 1; + di->m_type_name = strlen(DebugStrings[0]) + 1 + strlen(DebugStrings[1]) + 1 + strlen(DebugStrings[2]) + 1 + strlen(DebugStrings[3]) + 1; + + /* zero end marker */ + buf.MemPtr[buf.MemSize-1] = 0; + return true; +} + +/* Build the DWARF lookup section */ +bool NotifyGdb::BuildDebugPub(MemBuf& buf, const char* name, uint32_t size, uint32_t die_offset) +{ + uint32_t length = sizeof(DwarfPubHeader) + sizeof(uint32_t) + strlen(name) + 1 + sizeof(uint32_t); + + buf.MemSize = length; + buf.MemPtr = new (nothrow) char[buf.MemSize]; + + if (buf.MemPtr == nullptr) + return false; + + DwarfPubHeader* header = reinterpret_cast<DwarfPubHeader*>(buf.MemPtr.GetValue()); + header->m_length = length - sizeof(uint32_t); + header->m_version = 2; + header->m_debug_info_off = 0; + header->m_debug_info_len = size; + *reinterpret_cast<uint32_t*>(buf.MemPtr + sizeof(DwarfPubHeader)) = die_offset; + strcpy(buf.MemPtr + sizeof(DwarfPubHeader) + sizeof(uint32_t), name); + *reinterpret_cast<uint32_t*>(buf.MemPtr + length - sizeof(uint32_t)) = 0; + + return true; +} + +/* Build ELF string section */ +bool NotifyGdb::BuildSectionNameTable(MemBuf& buf) +{ + uint32_t totalLength = 0; + + /* calculate total size */ + for (int i = 0; i < SectionNamesCount; ++i) + { + totalLength += strlen(SectionNames[i]) + 1; + } + + buf.MemSize = totalLength; + buf.MemPtr = new (nothrow) char[totalLength]; + if (buf.MemPtr == nullptr) + return false; + + /* copy strings */ + char* bufPtr = buf.MemPtr; + for (int i = 0; i < SectionNamesCount; ++i) + { + strcpy(bufPtr, SectionNames[i]); + bufPtr += strlen(SectionNames[i]) + 1; + } + + return true; +} + +/* Build the ELF section headers table */ +bool NotifyGdb::BuildSectionTable(MemBuf& buf) +{ + Elf_Shdr* sectionHeaders = new (nothrow) Elf_Shdr[SectionNamesCount - 1]; + Elf_Shdr* pSh = sectionHeaders; + + if (sectionHeaders == nullptr) + { + return false; + } + + /* NULL entry */ + pSh->sh_name = 0; + pSh->sh_type = SHT_NULL; + pSh->sh_flags = 0; + pSh->sh_addr = 0; + pSh->sh_offset = 0; + pSh->sh_size = 0; + pSh->sh_link = SHN_UNDEF; + pSh->sh_info = 0; + pSh->sh_addralign = 0; + pSh->sh_entsize = 0; + + ++pSh; + /* fill section header data */ + uint32_t sectNameOffset = 1; + for (int i = 1; i < SectionNamesCount - 1; ++i, ++pSh) + { + pSh->sh_name = sectNameOffset; + sectNameOffset += strlen(SectionNames[i]) + 1; + pSh->sh_type = Sections[i].m_type; + pSh->sh_flags = Sections[i].m_flags; + pSh->sh_addr = 0; + pSh->sh_offset = 0; + pSh->sh_size = 0; + pSh->sh_link = SHN_UNDEF; + pSh->sh_info = 0; + pSh->sh_addralign = 1; + pSh->sh_entsize = 0; + } + + buf.MemPtr = reinterpret_cast<char*>(sectionHeaders); + buf.MemSize = sizeof(Elf_Shdr) * (SectionNamesCount - 1); + return true; +} + +/* Build the ELF header */ +bool NotifyGdb::BuildELFHeader(MemBuf& buf) +{ + Elf_Ehdr* header = new (nothrow) Elf_Ehdr; + buf.MemPtr = reinterpret_cast<char*>(header); + buf.MemSize = sizeof(Elf_Ehdr); + + if (header == nullptr) + return false; + + return true; + +} + +/* Split full path name into directory & file anmes */ +void NotifyGdb::SplitPathname(const char* path, const char*& pathName, const char*& fileName) +{ + char* pSlash = strrchr(path, '/'); + + if (pSlash != nullptr) + { + *pSlash = 0; + fileName = ++pSlash; + pathName = path; + } + else + { + fileName = path; + pathName = nullptr; + } +} + +/* LEB128 for 32-bit unsigned integer */ +int NotifyGdb::Leb128Encode(uint32_t num, char* buf, int size) +{ + int i = 0; + + do + { + uint8_t byte = num & 0x7F; + if (i >= size) + break; + num >>= 7; + if (num != 0) + byte |= 0x80; + buf[i++] = byte; + } + while (num != 0); + + return i; +} + +/* LEB128 for 32-bit signed integer */ +int NotifyGdb::Leb128Encode(int32_t num, char* buf, int size) +{ + int i = 0; + bool hasMore = true, isNegative = num < 0; + + while (hasMore && i < size) + { + uint8_t byte = num & 0x7F; + num >>= 7; + + if ((num == 0 && (byte & 0x40) == 0) || (num == -1 && (byte & 0x40) == 0x40)) + hasMore = false; + else + byte |= 0x80; + buf[i++] = byte; + } + + return i; +} + +/* ELF 32bit header */ +Elf32_Ehdr::Elf32_Ehdr() +{ + e_ident[EI_MAG0] = ElfMagic[0]; + e_ident[EI_MAG1] = ElfMagic[1]; + e_ident[EI_MAG2] = ElfMagic[2]; + e_ident[EI_MAG3] = ElfMagic[3]; + e_ident[EI_CLASS] = ELFCLASS32; + e_ident[EI_DATA] = ELFDATA2LSB; + e_ident[EI_VERSION] = EV_CURRENT; + e_ident[EI_OSABI] = ELFOSABI_NONE; + e_ident[EI_ABIVERSION] = 0; + for (int i = EI_PAD; i < EI_NIDENT; ++i) + e_ident[i] = 0; + + e_type = ET_REL; +#if defined(_TARGET_X86_) + e_machine = EM_386; +#elif defined(_TARGET_ARM_) + e_machine = EM_ARM; +#endif + e_flags = 0; + e_version = 1; + e_entry = 0; + e_phoff = 0; + e_ehsize = sizeof(Elf32_Ehdr); + e_phentsize = 0; + e_phnum = 0; +} + +/* ELF 64bit header */ +Elf64_Ehdr::Elf64_Ehdr() +{ + e_ident[EI_MAG0] = ElfMagic[0]; + e_ident[EI_MAG1] = ElfMagic[1]; + e_ident[EI_MAG2] = ElfMagic[2]; + e_ident[EI_MAG3] = ElfMagic[3]; + e_ident[EI_CLASS] = ELFCLASS64; + e_ident[EI_DATA] = ELFDATA2LSB; + e_ident[EI_VERSION] = EV_CURRENT; + e_ident[EI_OSABI] = ELFOSABI_NONE; + e_ident[EI_ABIVERSION] = 0; + for (int i = EI_PAD; i < EI_NIDENT; ++i) + e_ident[i] = 0; + + e_type = ET_REL; +#if defined(_TARGET_AMD64_) + e_machine = EM_X86_64; +#elif defined(_TARGET_ARM64_) + e_machine = EM_AARCH64; +#endif + e_flags = 0; + e_version = 1; + e_entry = 0; + e_phoff = 0; + e_ehsize = sizeof(Elf64_Ehdr); + e_phentsize = 0; + e_phnum = 0; +} diff --git a/src/vm/gdbjit.h b/src/vm/gdbjit.h new file mode 100644 index 0000000000..5c7f24b34c --- /dev/null +++ b/src/vm/gdbjit.h @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: gdbjit.h +// + +// +// Header file for GDB JIT interface implemenation. +// +//***************************************************************************** + + +#ifndef __GDBJIT_H__ +#define __GDBJIT_H__ + +#include <stdint.h> +#include "method.hpp" +#include "../inc/llvm/ELF.h" +#include "../inc/llvm/Dwarf.h" + +#if defined(_TARGET_X86_) || defined(_TARGET_ARM_) + typedef Elf32_Ehdr Elf_Ehdr; + typedef Elf32_Shdr Elf_Shdr; +#define ADDRESS_SIZE 4 +#elif defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) + typedef Elf64_Ehdr Elf_Ehdr; + typedef Elf64_Shdr Elf_Shdr; +#define ADDRESS_SIZE 8 +#else +#error "Target is not supported" +#endif + +struct __attribute__((packed)) DwarfCompUnit +{ + uint32_t m_length; + uint16_t m_version; + uint32_t m_abbrev_offset; + uint8_t m_addr_size; +}; + +struct __attribute__((packed)) DwarfPubHeader +{ + uint32_t m_length; + uint16_t m_version; + uint32_t m_debug_info_off; + uint32_t m_debug_info_len; +}; + +#define DW_LNS_MAX DW_LNS_set_isa + +struct __attribute__((packed)) DwarfLineNumHeader +{ + uint32_t m_length; + uint16_t m_version; + uint32_t m_hdr_length; + uint8_t m_min_instr_len; + uint8_t m_def_is_stmt; + int8_t m_line_base; + uint8_t m_line_range; + uint8_t m_opcode_base; + uint8_t m_std_num_arg[DW_LNS_MAX]; +}; + +struct SymbolsInfo +{ + int lineNumber, ilOffset, nativeOffset, fileIndex; + char fileName[2*MAX_PATH_FNAME]; +}; + + +class NotifyGdb +{ +public: + static void MethodCompiled(MethodDesc* MethodDescPtr); + static void MethodDropped(MethodDesc* MethodDescPtr); +private: + struct MemBuf + { + NewArrayHolder<char> MemPtr; + unsigned MemSize; + MemBuf() : MemPtr(0), MemSize(0) + {} + }; + + static bool BuildELFHeader(MemBuf& buf); + static bool BuildSectionNameTable(MemBuf& buf); + static bool BuildSectionTable(MemBuf& buf); + static bool BuildDebugStrings(MemBuf& buf); + static bool BuildDebugAbbrev(MemBuf& buf); + static bool BuildDebugInfo(MemBuf& buf); + static bool BuildDebugPub(MemBuf& buf, const char* name, uint32_t size, uint32_t dieOffset); + static bool BuildLineTable(MemBuf& buf, PCODE startAddr, SymbolsInfo* lines, unsigned nlines); + static bool BuildFileTable(MemBuf& buf, SymbolsInfo* lines, unsigned nlines); + static bool BuildLineProg(MemBuf& buf, PCODE startAddr, SymbolsInfo* lines, unsigned nlines); + static bool FitIntoSpecialOpcode(int8_t line_shift, uint8_t addr_shift); + static void IssueSetAddress(char*& ptr, PCODE addr); + static void IssueEndOfSequence(char*& ptr); + static void IssueSimpleCommand(char*& ptr, uint8_t command); + static void IssueParamCommand(char*& ptr, uint8_t command, char* param, int param_len); + static void IssueSpecialCommand(char*& ptr, int8_t line_shift, uint8_t addr_shift); + static void SplitPathname(const char* path, const char*& pathName, const char*& fileName); + static int Leb128Encode(uint32_t num, char* buf, int size); + static int Leb128Encode(int32_t num, char* buf, int size); +}; + +#endif // #ifndef __GDBJIT_H__ diff --git a/src/vm/gdbjithelpers.h b/src/vm/gdbjithelpers.h new file mode 100644 index 0000000000..1fa5d4674e --- /dev/null +++ b/src/vm/gdbjithelpers.h @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: gdbjithelpers.h +// +// +// Helper file with managed delegate for GDB JIT interface implemenation. +// +//***************************************************************************** + + +#ifndef __GDBJITHELPERS_H__ +#define __GDBJITHELPERS_H__ + +struct SequencePointInfo +{ + int lineNumber, ilOffset; + char16_t* fileName; +}; + +struct MethodDebugInfo +{ + SequencePointInfo* points; + int size; +}; + +typedef int (*GetInfoForMethodDelegate)(const char*, unsigned int, MethodDebugInfo& methodDebugInfo); +extern GetInfoForMethodDelegate getInfoForMethodDelegate; + +#endif // !__GDBJITHELPERS_H__ diff --git a/src/vm/util.cpp b/src/vm/util.cpp index f7185c744f..e0f09e3ae0 100644 --- a/src/vm/util.cpp +++ b/src/vm/util.cpp @@ -3399,6 +3399,11 @@ void InitializeClrNotifications() #pragma optimize("", off) #endif // _MSC_VER +#if defined(FEATURE_GDBJIT) +#include "gdbjit.h" +__declspec(thread) bool tls_isSymReaderInProgress = false; +#endif // FEATURE_GDBJIT + // called from the runtime void DACNotify::DoJITNotification(MethodDesc *MethodDescPtr) { @@ -3410,7 +3415,14 @@ void DACNotify::DoJITNotification(MethodDesc *MethodDescPtr) MODE_PREEMPTIVE; } CONTRACTL_END; - +#if defined(FEATURE_GDBJIT) && defined(FEATURE_PAL) && !defined(CROSSGEN_COMPILE) + if(!tls_isSymReaderInProgress) + { + tls_isSymReaderInProgress = true; + NotifyGdb::MethodCompiled(MethodDescPtr); + tls_isSymReaderInProgress = false; + } +#endif TADDR Args[2] = { JIT_NOTIFICATION, (TADDR) MethodDescPtr }; DACNotifyExceptionHelper(Args, 2); } @@ -3426,6 +3438,9 @@ void DACNotify::DoJITDiscardNotification(MethodDesc *MethodDescPtr) } CONTRACTL_END; +#if defined(FEATURE_GDBJIT) && defined(FEATURE_PAL) && !defined(CROSSGEN_COMPILE) + NotifyGdb::MethodDropped(MethodDescPtr); +#endif TADDR Args[2] = { JIT_DISCARD_NOTIFICATION, (TADDR) MethodDescPtr }; DACNotifyExceptionHelper(Args, 2); } |