| /* | |
| * Read VC++ debug information from COFF and eventually | |
| * from PDB files. | |
| * | |
| * Copyright (C) 1996, Eric Youngdale. | |
| * Copyright (C) 1999-2000, Ulrich Weigand. | |
| * Copyright (C) 2004, Eric Pouech. | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| /* | |
| * Note - this handles reading debug information for 32 bit applications | |
| * that run under Windows-NT for example. I doubt that this would work well | |
| * for 16 bit applications, but I don't think it really matters since the | |
| * file format is different, and we should never get in here in such cases. | |
| * | |
| * TODO: | |
| * Get 16 bit CV stuff working. | |
| * Add symbol size to internal symbol table. | |
| */ | |
| #include "config.h" | |
| #include "wine/port.h" | |
| #include <assert.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifdef HAVE_UNISTD_H | |
| # include <unistd.h> | |
| #endif | |
| #ifndef PATH_MAX | |
| #define PATH_MAX MAX_PATH | |
| #endif | |
| #include <stdarg.h> | |
| #include "windef.h" | |
| #include "winbase.h" | |
| #include "winternl.h" | |
| #include "wine/exception.h" | |
| #include "wine/debug.h" | |
| #include "dbghelp_private.h" | |
| #include "wine/mscvpdb.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(dbghelp_coff); | |
| /*======================================================================== | |
| * Process COFF debug information. | |
| */ | |
| struct CoffFile | |
| { | |
| unsigned int startaddr; | |
| unsigned int endaddr; | |
| struct symt_compiland* compiland; | |
| int linetab_offset; | |
| int linecnt; | |
| struct symt** entries; | |
| int neps; | |
| int neps_alloc; | |
| }; | |
| struct CoffFileSet | |
| { | |
| struct CoffFile* files; | |
| int nfiles; | |
| int nfiles_alloc; | |
| }; | |
| static const char* coff_get_name(const IMAGE_SYMBOL* coff_sym, | |
| const char* coff_strtab) | |
| { | |
| static char namebuff[9]; | |
| const char* nampnt; | |
| if (coff_sym->N.Name.Short) | |
| { | |
| memcpy(namebuff, coff_sym->N.ShortName, 8); | |
| namebuff[8] = '\0'; | |
| nampnt = &namebuff[0]; | |
| } | |
| else | |
| { | |
| nampnt = coff_strtab + coff_sym->N.Name.Long; | |
| } | |
| if (nampnt[0] == '_') nampnt++; | |
| return nampnt; | |
| } | |
| static int coff_add_file(struct CoffFileSet* coff_files, struct module* module, | |
| const char* filename) | |
| { | |
| struct CoffFile* file; | |
| if (coff_files->nfiles + 1 >= coff_files->nfiles_alloc) | |
| { | |
| coff_files->nfiles_alloc += 10; | |
| coff_files->files = (coff_files->files) ? | |
| HeapReAlloc(GetProcessHeap(), 0, coff_files->files, | |
| coff_files->nfiles_alloc * sizeof(struct CoffFile)) : | |
| HeapAlloc(GetProcessHeap(), 0, | |
| coff_files->nfiles_alloc * sizeof(struct CoffFile)); | |
| } | |
| file = coff_files->files + coff_files->nfiles; | |
| file->startaddr = 0xffffffff; | |
| file->endaddr = 0; | |
| file->compiland = symt_new_compiland(module, 0, | |
| source_new(module, NULL, filename)); | |
| file->linetab_offset = -1; | |
| file->linecnt = 0; | |
| file->entries = NULL; | |
| file->neps = file->neps_alloc = 0; | |
| return coff_files->nfiles++; | |
| } | |
| static void coff_add_symbol(struct CoffFile* coff_file, struct symt* sym) | |
| { | |
| if (coff_file->neps + 1 >= coff_file->neps_alloc) | |
| { | |
| coff_file->neps_alloc += 10; | |
| coff_file->entries = (coff_file->entries) ? | |
| HeapReAlloc(GetProcessHeap(), 0, coff_file->entries, | |
| coff_file->neps_alloc * sizeof(struct symt*)) : | |
| HeapAlloc(GetProcessHeap(), 0, | |
| coff_file->neps_alloc * sizeof(struct symt*)); | |
| } | |
| coff_file->entries[coff_file->neps++] = sym; | |
| } | |
| BOOL coff_process_info(const struct msc_debug_info* msc_dbg) | |
| { | |
| const IMAGE_AUX_SYMBOL* aux; | |
| const IMAGE_COFF_SYMBOLS_HEADER* coff; | |
| const IMAGE_LINENUMBER* coff_linetab; | |
| const IMAGE_LINENUMBER* linepnt; | |
| const char* coff_strtab; | |
| const IMAGE_SYMBOL* coff_sym; | |
| const IMAGE_SYMBOL* coff_symbols; | |
| struct CoffFileSet coff_files; | |
| int curr_file_idx = -1; | |
| unsigned int i; | |
| int j; | |
| int k; | |
| int l; | |
| int linetab_indx; | |
| const char* nampnt; | |
| int naux; | |
| BOOL ret = FALSE; | |
| DWORD addr; | |
| TRACE("Processing COFF symbols...\n"); | |
| assert(sizeof(IMAGE_SYMBOL) == IMAGE_SIZEOF_SYMBOL); | |
| assert(sizeof(IMAGE_LINENUMBER) == IMAGE_SIZEOF_LINENUMBER); | |
| coff_files.files = NULL; | |
| coff_files.nfiles = coff_files.nfiles_alloc = 0; | |
| coff = (const IMAGE_COFF_SYMBOLS_HEADER*)msc_dbg->root; | |
| coff_symbols = (const IMAGE_SYMBOL*)((const char *)coff + coff->LvaToFirstSymbol); | |
| coff_linetab = (const IMAGE_LINENUMBER*)((const char *)coff + coff->LvaToFirstLinenumber); | |
| coff_strtab = (const char*)(coff_symbols + coff->NumberOfSymbols); | |
| linetab_indx = 0; | |
| for (i = 0; i < coff->NumberOfSymbols; i++) | |
| { | |
| coff_sym = coff_symbols + i; | |
| naux = coff_sym->NumberOfAuxSymbols; | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_FILE) | |
| { | |
| curr_file_idx = coff_add_file(&coff_files, msc_dbg->module, | |
| (const char*)(coff_sym + 1)); | |
| TRACE("New file %s\n", (const char*)(coff_sym + 1)); | |
| i += naux; | |
| continue; | |
| } | |
| if (curr_file_idx < 0) | |
| { | |
| assert(coff_files.nfiles == 0 && coff_files.nfiles_alloc == 0); | |
| curr_file_idx = coff_add_file(&coff_files, msc_dbg->module, "<none>"); | |
| TRACE("New file <none>\n"); | |
| } | |
| /* | |
| * This guy marks the size and location of the text section | |
| * for the current file. We need to keep track of this so | |
| * we can figure out what file the different global functions | |
| * go with. | |
| */ | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_STATIC && | |
| naux != 0 && coff_sym->Type == 0 && coff_sym->SectionNumber == 1) | |
| { | |
| aux = (const IMAGE_AUX_SYMBOL*) (coff_sym + 1); | |
| if (coff_files.files[curr_file_idx].linetab_offset != -1) | |
| { | |
| /* | |
| * Save this so we can still get the old name. | |
| */ | |
| const char* fn; | |
| fn = source_get(msc_dbg->module, | |
| coff_files.files[curr_file_idx].compiland->source); | |
| TRACE("Duplicating sect from %s: %x %x %x %d %d\n", | |
| fn, aux->Section.Length, | |
| aux->Section.NumberOfRelocations, | |
| aux->Section.NumberOfLinenumbers, | |
| aux->Section.Number, aux->Section.Selection); | |
| TRACE("More sect %d %s %08x %d %d %d\n", | |
| coff_sym->SectionNumber, | |
| coff_get_name(coff_sym, coff_strtab), | |
| coff_sym->Value, coff_sym->Type, | |
| coff_sym->StorageClass, coff_sym->NumberOfAuxSymbols); | |
| /* | |
| * Duplicate the file entry. We have no way to describe | |
| * multiple text sections in our current way of handling things. | |
| */ | |
| coff_add_file(&coff_files, msc_dbg->module, fn); | |
| } | |
| else | |
| { | |
| TRACE("New text sect from %s: %x %x %x %d %d\n", | |
| source_get(msc_dbg->module, coff_files.files[curr_file_idx].compiland->source), | |
| aux->Section.Length, | |
| aux->Section.NumberOfRelocations, | |
| aux->Section.NumberOfLinenumbers, | |
| aux->Section.Number, aux->Section.Selection); | |
| } | |
| if (coff_files.files[curr_file_idx].startaddr > coff_sym->Value) | |
| { | |
| coff_files.files[curr_file_idx].startaddr = coff_sym->Value; | |
| } | |
| if (coff_files.files[curr_file_idx].endaddr < coff_sym->Value + aux->Section.Length) | |
| { | |
| coff_files.files[curr_file_idx].endaddr = coff_sym->Value + aux->Section.Length; | |
| } | |
| coff_files.files[curr_file_idx].linetab_offset = linetab_indx; | |
| coff_files.files[curr_file_idx].linecnt = aux->Section.NumberOfLinenumbers; | |
| linetab_indx += aux->Section.NumberOfLinenumbers; | |
| i += naux; | |
| continue; | |
| } | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_STATIC && naux == 0 && | |
| coff_sym->SectionNumber == 1) | |
| { | |
| DWORD base = msc_dbg->sectp[coff_sym->SectionNumber - 1].VirtualAddress; | |
| /* | |
| * This is a normal static function when naux == 0. | |
| * Just register it. The current file is the correct | |
| * one in this instance. | |
| */ | |
| nampnt = coff_get_name(coff_sym, coff_strtab); | |
| TRACE("\tAdding static symbol %s\n", nampnt); | |
| /* FIXME: was adding symbol to this_file ??? */ | |
| coff_add_symbol(&coff_files.files[curr_file_idx], | |
| &symt_new_function(msc_dbg->module, | |
| coff_files.files[curr_file_idx].compiland, | |
| nampnt, | |
| msc_dbg->module->module.BaseOfImage + base + coff_sym->Value, | |
| 0 /* FIXME */, | |
| NULL /* FIXME */)->symt); | |
| i += naux; | |
| continue; | |
| } | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_EXTERNAL && | |
| ISFCN(coff_sym->Type) && coff_sym->SectionNumber > 0) | |
| { | |
| struct symt_compiland* compiland = NULL; | |
| DWORD base = msc_dbg->sectp[coff_sym->SectionNumber - 1].VirtualAddress; | |
| nampnt = coff_get_name(coff_sym, coff_strtab); | |
| TRACE("%d: %s %s\n", | |
| i, wine_dbgstr_longlong(msc_dbg->module->module.BaseOfImage + base + coff_sym->Value), | |
| nampnt); | |
| TRACE("\tAdding global symbol %s (sect=%s)\n", | |
| nampnt, msc_dbg->sectp[coff_sym->SectionNumber - 1].Name); | |
| /* | |
| * Now we need to figure out which file this guy belongs to. | |
| */ | |
| for (j = 0; j < coff_files.nfiles; j++) | |
| { | |
| if (coff_files.files[j].startaddr <= base + coff_sym->Value | |
| && coff_files.files[j].endaddr > base + coff_sym->Value) | |
| { | |
| compiland = coff_files.files[j].compiland; | |
| break; | |
| } | |
| } | |
| if (j < coff_files.nfiles) | |
| { | |
| coff_add_symbol(&coff_files.files[j], | |
| &symt_new_function(msc_dbg->module, compiland, nampnt, | |
| msc_dbg->module->module.BaseOfImage + base + coff_sym->Value, | |
| 0 /* FIXME */, NULL /* FIXME */)->symt); | |
| } | |
| else | |
| { | |
| symt_new_function(msc_dbg->module, NULL, nampnt, | |
| msc_dbg->module->module.BaseOfImage + base + coff_sym->Value, | |
| 0 /* FIXME */, NULL /* FIXME */); | |
| } | |
| i += naux; | |
| continue; | |
| } | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_EXTERNAL && | |
| coff_sym->SectionNumber > 0) | |
| { | |
| DWORD base = msc_dbg->sectp[coff_sym->SectionNumber - 1].VirtualAddress; | |
| /* | |
| * Similar to above, but for the case of data symbols. | |
| * These aren't treated as entrypoints. | |
| */ | |
| nampnt = coff_get_name(coff_sym, coff_strtab); | |
| TRACE("%d: %s %s\n", | |
| i, wine_dbgstr_longlong(msc_dbg->module->module.BaseOfImage + base + coff_sym->Value), | |
| nampnt); | |
| TRACE("\tAdding global data symbol %s\n", nampnt); | |
| /* | |
| * Now we need to figure out which file this guy belongs to. | |
| */ | |
| symt_new_global_variable(msc_dbg->module, NULL, nampnt, TRUE /* FIXME */, | |
| msc_dbg->module->module.BaseOfImage + base + coff_sym->Value, | |
| 0 /* FIXME */, NULL /* FIXME */); | |
| i += naux; | |
| continue; | |
| } | |
| if (coff_sym->StorageClass == IMAGE_SYM_CLASS_STATIC && naux == 0) | |
| { | |
| /* | |
| * Ignore these. They don't have anything to do with | |
| * reality. | |
| */ | |
| i += naux; | |
| continue; | |
| } | |
| TRACE("Skipping unknown entry '%s' %d %d %d\n", | |
| coff_get_name(coff_sym, coff_strtab), | |
| coff_sym->StorageClass, coff_sym->SectionNumber, naux); | |
| /* | |
| * For now, skip past the aux entries. | |
| */ | |
| i += naux; | |
| } | |
| if (coff_files.files != NULL) | |
| { | |
| /* | |
| * OK, we now should have a list of files, and we should have a list | |
| * of entrypoints. We need to sort the entrypoints so that we are | |
| * able to tie the line numbers with the given functions within the | |
| * file. | |
| */ | |
| for (j = 0; j < coff_files.nfiles; j++) | |
| { | |
| if (coff_files.files[j].entries != NULL) | |
| { | |
| qsort(coff_files.files[j].entries, coff_files.files[j].neps, | |
| sizeof(struct symt*), symt_cmp_addr); | |
| } | |
| } | |
| /* | |
| * Now pick apart the line number tables, and attach the entries | |
| * to the given functions. | |
| */ | |
| for (j = 0; j < coff_files.nfiles; j++) | |
| { | |
| l = 0; | |
| if (coff_files.files[j].neps != 0) | |
| { | |
| for (k = 0; k < coff_files.files[j].linecnt; k++) | |
| { | |
| linepnt = coff_linetab + coff_files.files[j].linetab_offset + k; | |
| /* | |
| * If we have spilled onto the next entrypoint, then | |
| * bump the counter.. | |
| */ | |
| for (;;) | |
| { | |
| if (l+1 >= coff_files.files[j].neps) break; | |
| symt_get_info(coff_files.files[j].entries[l+1], TI_GET_ADDRESS, &addr); | |
| if (((msc_dbg->module->module.BaseOfImage + linepnt->Type.VirtualAddress) < addr)) | |
| break; | |
| l++; | |
| } | |
| if (coff_files.files[j].entries[l+1]->tag == SymTagFunction) | |
| { | |
| /* | |
| * Add the line number. This is always relative to the | |
| * start of the function, so we need to subtract that offset | |
| * first. | |
| */ | |
| symt_get_info(coff_files.files[j].entries[l+1], TI_GET_ADDRESS, &addr); | |
| symt_add_func_line(msc_dbg->module, (struct symt_function*)coff_files.files[j].entries[l+1], | |
| coff_files.files[j].compiland->source, linepnt->Linenumber, | |
| msc_dbg->module->module.BaseOfImage + linepnt->Type.VirtualAddress - addr); | |
| } | |
| } | |
| } | |
| } | |
| for (j = 0; j < coff_files.nfiles; j++) | |
| { | |
| HeapFree(GetProcessHeap(), 0, coff_files.files[j].entries); | |
| } | |
| HeapFree(GetProcessHeap(), 0, coff_files.files); | |
| msc_dbg->module->module.SymType = SymCoff; | |
| /* FIXME: we could have a finer grain here */ | |
| msc_dbg->module->module.LineNumbers = TRUE; | |
| msc_dbg->module->module.GlobalSymbols = TRUE; | |
| msc_dbg->module->module.TypeInfo = FALSE; | |
| msc_dbg->module->module.SourceIndexed = TRUE; | |
| msc_dbg->module->module.Publics = TRUE; | |
| ret = TRUE; | |
| } | |
| return ret; | |
| } |