blob: 0fbdcab7b5b01abc7181fac1c113c09dd6ed14df [file] [log] [blame]
/* ----------------------------------------------------------------------- *
*
* Copyright 1996-2017 The NASM Authors - All Rights Reserved
* See the file AUTHORS included with the NASM distribution for
* the specific copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ----------------------------------------------------------------------- */
/*
* codeview.c Codeview Debug Format support for COFF
*/
#include "version.h"
#include "compiler.h"
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include "nasm.h"
#include "nasmlib.h"
#include "error.h"
#include "preproc.h"
#include "saa.h"
#include "hashtbl.h"
#include "outlib.h"
#include "pecoff.h"
#include "md5.h"
static void cv8_init(void);
static void cv8_linenum(const char *filename, int32_t linenumber,
int32_t segto);
static void cv8_deflabel(char *name, int32_t segment, int64_t offset,
int is_global, char *special);
static void cv8_typevalue(int32_t type);
static void cv8_output(int type, void *param);
static void cv8_cleanup(void);
const struct dfmt df_cv8 = {
"Codeview 8", /* .fullname */
"cv8", /* .shortname */
cv8_init, /* .init */
cv8_linenum, /* .linenum */
cv8_deflabel, /* .debug_deflabel */
null_debug_directive, /* .debug_directive */
cv8_typevalue, /* .debug_typevalue */
cv8_output, /* .debug_output */
cv8_cleanup, /* .cleanup */
NULL /* pragma list */
};
/*******************************************************************************
* dfmt callbacks
******************************************************************************/
struct source_file;
struct source_file {
const char *filename;
char *fullname;
uint32_t fullnamelen;
struct source_file *next;
uint32_t filetbl_off;
uint32_t sourcetbl_off;
struct SAA *lines;
uint32_t num_lines;
unsigned char md5sum[MD5_HASHBYTES];
};
struct linepair {
uint32_t file_offset;
uint32_t linenumber;
};
enum symbol_type {
SYMTYPE_CODE,
SYMTYPE_PROC,
SYMTYPE_LDATA,
SYMTYPE_GDATA,
SYMTYPE_MAX
};
struct cv8_symbol {
enum symbol_type type;
char *name;
uint32_t secrel;
uint16_t section;
uint32_t size;
uint32_t typeindex;
enum symtype {
TYPE_UNREGISTERED = 0x0000, /* T_NOTYPE */
TYPE_BYTE = 0x0020,
TYPE_WORD = 0x0021,
TYPE_DWORD= 0x0022,
TYPE_QUAD = 0x0023,
TYPE_REAL32 = 0x0040,
TYPE_REAL64 = 0x0041,
TYPE_REAL80 = 0x0042,
TYPE_REAL128= 0x0043,
TYPE_REAL256= 0x0044,
TYPE_REAL512= 0x0045
} symtype;
};
struct cv8_state {
int symbol_sect;
int type_sect;
uint32_t text_offset;
struct source_file *source_files, **source_files_tail;
const char *last_filename;
struct source_file *last_source_file;
struct hash_table file_hash;
unsigned num_files;
uint32_t total_filename_len;
unsigned total_lines;
struct SAA *symbols;
struct cv8_symbol *last_sym;
unsigned num_syms[SYMTYPE_MAX];
unsigned symbol_lengths;
unsigned total_syms;
struct {
char *name;
size_t namebytes;
} outfile;
};
struct cv8_state cv8_state;
static void cv8_init(void)
{
const uint32_t sect_flags = IMAGE_SCN_MEM_READ |
IMAGE_SCN_MEM_DISCARDABLE |
IMAGE_SCN_CNT_INITIALIZED_DATA |
IMAGE_SCN_ALIGN_1BYTES;
cv8_state.symbol_sect = coff_make_section(".debug$S", sect_flags);
cv8_state.type_sect = coff_make_section(".debug$T", sect_flags);
cv8_state.text_offset = 0;
cv8_state.source_files = NULL;
cv8_state.source_files_tail = &cv8_state.source_files;
hash_init(&cv8_state.file_hash, HASH_MEDIUM);
cv8_state.num_files = 0;
cv8_state.total_filename_len = 0;
cv8_state.total_lines = 0;
cv8_state.symbols = saa_init(sizeof(struct cv8_symbol));
cv8_state.last_sym = NULL;
}
static struct source_file *register_file(const char *filename);
static struct coff_Section *find_section(int32_t segto);
static void cv8_linenum(const char *filename, int32_t linenumber,
int32_t segto)
{
struct coff_Section *s;
struct linepair *li;
struct source_file *file;
file = register_file(filename);
s = find_section(segto);
if (s == NULL)
return;
if ((s->flags & IMAGE_SCN_MEM_EXECUTE) == 0)
return;
li = saa_wstruct(file->lines);
li->file_offset = cv8_state.text_offset;
li->linenumber = linenumber;
file->num_lines++;
cv8_state.total_lines++;
}
static void cv8_deflabel(char *name, int32_t segment, int64_t offset,
int is_global, char *special)
{
struct cv8_symbol *sym;
struct coff_Section *s;
(void)special;
s = find_section(segment);
if (s == NULL)
return;
sym = saa_wstruct(cv8_state.symbols);
if (s->flags & IMAGE_SCN_MEM_EXECUTE)
sym->type = is_global ? SYMTYPE_PROC : SYMTYPE_CODE;
else
sym->type = is_global ? SYMTYPE_GDATA : SYMTYPE_LDATA;
cv8_state.num_syms[sym->type]++;
cv8_state.total_syms++;
sym->section = segment;
sym->secrel = offset;
sym->symtype = TYPE_UNREGISTERED;
sym->size = 0;
sym->typeindex = 0;
sym->name = nasm_strdup(name);
cv8_state.symbol_lengths += strlen(sym->name) + 1;
if (cv8_state.last_sym && cv8_state.last_sym->section == segment)
cv8_state.last_sym->size = offset - cv8_state.last_sym->secrel;
cv8_state.last_sym = sym;
}
static void cv8_typevalue(int32_t type)
{
if (!cv8_state.last_sym)
return;
if (cv8_state.last_sym->symtype != TYPE_UNREGISTERED)
return;
switch (TYM_TYPE(type)) {
case TY_BYTE:
cv8_state.last_sym->symtype = TYPE_BYTE;
break;
case TY_WORD:
cv8_state.last_sym->symtype = TYPE_WORD;
break;
case TY_DWORD:
cv8_state.last_sym->symtype = TYPE_DWORD;
break;
case TY_QWORD:
cv8_state.last_sym->symtype = TYPE_QUAD;
break;
case TY_FLOAT:
cv8_state.last_sym->symtype = TYPE_REAL32;
break;
case TY_TBYTE:
cv8_state.last_sym->symtype = TYPE_REAL80;
break;
case TY_OWORD:
cv8_state.last_sym->symtype = TYPE_REAL128;
break;
case TY_YWORD:
cv8_state.last_sym->symtype = TYPE_REAL256;
break;
case TY_ZWORD:
cv8_state.last_sym->symtype = TYPE_REAL512;
break;
case TY_UNKNOWN:
break;
case TY_LABEL:
break;
}
}
static void cv8_output(int type, void *param)
{
struct coff_DebugInfo *dinfo = param;
(void)type;
if (dinfo->section && dinfo->section->name &&
!strncmp(dinfo->section->name, ".text", 5))
cv8_state.text_offset += dinfo->size;
}
static void build_symbol_table(struct coff_Section *const sect);
static void build_type_table(struct coff_Section *const sect);
static void cv8_cleanup(void)
{
struct cv8_symbol *sym;
struct source_file *file;
struct coff_Section *symbol_sect = coff_sects[cv8_state.symbol_sect];
struct coff_Section *type_sect = coff_sects[cv8_state.type_sect];
cv8_state.outfile.name = nasm_realpath(outname);
cv8_state.outfile.namebytes = strlen(cv8_state.outfile.name) + 1;
build_symbol_table(symbol_sect);
build_type_table(type_sect);
list_for_each(file, cv8_state.source_files) {
nasm_free(file->fullname);
saa_free(file->lines);
free(file);
}
hash_free(&cv8_state.file_hash);
saa_rewind(cv8_state.symbols);
while ((sym = saa_rstruct(cv8_state.symbols)))
nasm_free(sym->name);
saa_free(cv8_state.symbols);
nasm_free(cv8_state.outfile.name);
}
/*******************************************************************************
* implementation
******************************************************************************/
static void calc_md5(const char *const filename,
unsigned char sum[MD5_HASHBYTES])
{
int success = 0;
unsigned char *file_buf;
FILE *f;
MD5_CTX ctx;
f = pp_input_fopen(filename, NF_BINARY);
if (!f)
goto done;
file_buf = nasm_zalloc(BUFSIZ);
MD5Init(&ctx);
while (!feof(f)) {
size_t i = fread(file_buf, 1, BUFSIZ, f);
if (ferror(f))
goto done_0;
else if (i == 0)
break;
MD5Update(&ctx, file_buf, i);
}
MD5Final(sum, &ctx);
success = 1;
done_0:
nasm_free(file_buf);
fclose(f);
done:
if (!success) {
nasm_error(ERR_NONFATAL, "unable to hash file %s. "
"Debug information may be unavailable.\n",
filename);
}
return;
}
static struct source_file *register_file(const char *filename)
{
struct source_file *file;
void **filep;
char *fullpath;
struct hash_insert hi;
/*
* The common case is that we are invoked with the same filename
* as we were last time. Make this a pointer comparison: this is
* safe because the NASM core code allocates each filename once
* and never frees it.
*/
if (likely(cv8_state.last_filename == filename))
return cv8_state.last_source_file;
cv8_state.last_filename = filename;
filep = hash_find(&cv8_state.file_hash, filename, &hi);
if (likely(filep)) {
file = *filep;
} else {
/* New filename encounter */
fullpath = nasm_realpath(filename);
file = nasm_zalloc(sizeof(*file));
file->filename = filename;
file->fullname = fullpath;
file->fullnamelen = strlen(fullpath);
file->lines = saa_init(sizeof(struct linepair));
*cv8_state.source_files_tail = file;
cv8_state.source_files_tail = &file->next;
calc_md5(fullpath, file->md5sum);
hash_add(&hi, filename, file);
cv8_state.num_files++;
cv8_state.total_filename_len += file->fullnamelen + 1;
}
cv8_state.last_source_file = file;
return file;
}
static struct coff_Section *find_section(int32_t segto)
{
int i;
for (i = 0; i < coff_nsects; i++) {
struct coff_Section *sec;
sec = coff_sects[i];
if (segto == sec->index)
return sec;
}
return NULL;
}
static void register_reloc(struct coff_Section *const sect,
char *sym, uint32_t addr, uint16_t type)
{
struct coff_Reloc *r;
struct coff_Section *sec;
uint32_t i;
r = *sect->tail = nasm_malloc(sizeof(struct coff_Reloc));
sect->tail = &r->next;
r->next = NULL;
sect->nrelocs++;
r->address = addr;
r->symbase = SECT_SYMBOLS;
r->type = type;
r->symbol = 0;
for (i = 0; i < (uint32_t)coff_nsects; i++) {
sec = coff_sects[i];
if (!strcmp(sym, sec->name)) {
return;
}
r->symbol += 2;
}
saa_rewind(coff_syms);
for (i = 0; i < coff_nsyms; i++) {
struct coff_Symbol *s = saa_rstruct(coff_syms);
r->symbol++;
if (s->strpos == -1 && !strcmp(sym, s->name)) {
return;
} else if (s->strpos != -1) {
int res;
char *symname;
symname = nasm_malloc(s->namlen + 1);
saa_fread(coff_strs, s->strpos-4, symname, s->namlen);
symname[s->namlen] = '\0';
res = strcmp(sym, symname);
nasm_free(symname);
if (!res)
return;
}
}
nasm_panic("codeview: relocation for unregistered symbol: %s", sym);
}
static inline void section_write32(struct coff_Section *sect, uint32_t val)
{
saa_write32(sect->data, val);
sect->len += 4;
}
static inline void section_write16(struct coff_Section *sect, uint16_t val)
{
saa_write16(sect->data, val);
sect->len += 2;
}
static inline void section_write8(struct coff_Section *sect, uint8_t val)
{
saa_write8(sect->data, val);
sect->len++;
}
static inline void section_wbytes(struct coff_Section *sect, const void *buf,
size_t len)
{
saa_wbytes(sect->data, buf, len);
sect->len += len;
}
static void write_filename_table(struct coff_Section *const sect)
{
uint32_t field_length;
uint32_t tbl_off = 1; /* offset starts at 1 to skip NULL entry */
struct source_file *file;
nasm_assert(cv8_state.source_files != NULL);
nasm_assert(cv8_state.num_files > 0);
nasm_assert(cv8_state.total_filename_len > 0);
field_length = 1 + cv8_state.total_filename_len;
section_write32(sect, 0x000000F3);
section_write32(sect, field_length);
section_write8(sect, 0);
list_for_each(file, cv8_state.source_files) {
section_wbytes(sect, file->fullname, file->fullnamelen + 1);
file->filetbl_off = tbl_off;
tbl_off += file->fullnamelen + 1;
}
}
static void write_sourcefile_table(struct coff_Section *const sect)
{
const uint32_t entry_size = 4 + 2 + MD5_HASHBYTES + 2;
uint32_t field_length = 0;
uint32_t tbl_off = 0;
struct source_file *file;
field_length = entry_size * cv8_state.num_files;
section_write32(sect, 0x000000F4);
section_write32(sect, field_length);
list_for_each(file, cv8_state.source_files) {
nasm_assert(file->filetbl_off > 0);
section_write32(sect, file->filetbl_off);
section_write16(sect, 0x0110);
section_wbytes(sect, file->md5sum, MD5_HASHBYTES);
section_write16(sect, 0);
file->sourcetbl_off = tbl_off;
tbl_off += entry_size;
}
}
static void write_linenumber_table(struct coff_Section *const sect)
{
const uint32_t file_field_len = 12;
const uint32_t line_field_len = 8;
int i;
uint32_t field_length = 0;
size_t field_base;
struct source_file *file;
struct coff_Section *s;
for (i = 0; i < coff_nsects; i++) {
if (!strncmp(coff_sects[i]->name, ".text", 5))
break;
}
if (i == coff_nsects)
return;
s = coff_sects[i];
field_length = 12;
field_length += (cv8_state.num_files * file_field_len);
field_length += (cv8_state.total_lines * line_field_len);
section_write32(sect, 0x000000F2);
section_write32(sect, field_length);
field_base = sect->len;
section_write32(sect, 0); /* SECREL, updated by relocation */
section_write16(sect, 0); /* SECTION, updated by relocation*/
section_write16(sect, 0); /* pad */
section_write32(sect, s->len);
register_reloc(sect, ".text", field_base,
win64 ? IMAGE_REL_AMD64_SECREL : IMAGE_REL_I386_SECREL);
register_reloc(sect, ".text", field_base + 4,
win64 ? IMAGE_REL_AMD64_SECTION : IMAGE_REL_I386_SECTION);
list_for_each(file, cv8_state.source_files) {
struct linepair *li;
/* source mapping */
section_write32(sect, file->sourcetbl_off);
section_write32(sect, file->num_lines);
section_write32(sect, file_field_len + (file->num_lines * line_field_len));
/* the pairs */
saa_rewind(file->lines);
while ((li = saa_rstruct(file->lines))) {
section_write32(sect, li->file_offset);
section_write32(sect, li->linenumber |= 0x80000000);
}
}
}
static uint16_t write_symbolinfo_obj(struct coff_Section *sect)
{
uint16_t obj_len;
obj_len = 2 + 4 + cv8_state.outfile.namebytes;
section_write16(sect, obj_len);
section_write16(sect, 0x1101);
section_write32(sect, 0); /* ASM language */
section_wbytes(sect, cv8_state.outfile.name, cv8_state.outfile.namebytes);
return obj_len;
}
static uint16_t write_symbolinfo_properties(struct coff_Section *sect,
const char *const creator_str)
{
/* https://github.com/Microsoft/microsoft-pdb/blob/1d60e041/include/cvinfo.h#L3313 */
uint16_t creator_len;
creator_len = 2 + 4 + 2 + 3*2 + 3*2 + strlen(creator_str)+1 + 2;
/*
* We used to use a language ID of 3 for "MASM", since it's closest of the
* options available; however, BinScope from WACK (the Windows Application
* Certification Kit) tests for specific minimum MASM versions and trying to
* match an increasing sequence of random MASM version/build numbers seems
* like a fool's errand.
*
* Instead, use a different language ID (NASM is, after all, not MASM
* syntax) and just write the actual NASM version number. BinScope appears
* to be happy with that.
*/
section_write16(sect, creator_len);
section_write16(sect, 0x1116);
section_write32(sect, 'N'); /* language: 'N' (0x4e) for "NASM"; flags are 0 */
if (win64)
section_write16(sect, 0x00D0); /* machine */
else if (win32)
section_write16(sect, 0x0006); /* machine */
else
nasm_assert(!"neither win32 nor win64 are set!");
section_write16(sect, 0); /* verFEMajor */
section_write16(sect, 0); /* verFEMinor */
section_write16(sect, 0); /* verFEBuild */
/* BinScope/WACK insist on version >= 8.0.50727 */
section_write16(sect, NASM_MAJOR_VER); /* verMajor */
section_write16(sect, NASM_MINOR_VER); /* verMinor */
section_write16(sect, NASM_SUBMINOR_VER*100 + NASM_PATCHLEVEL_VER); /* verBuild */
section_wbytes(sect, creator_str, strlen(creator_str)+1); /* verSt */
/*
* normally there would be key/value pairs here, but they aren't
* necessary. They are terminated by 2B
*/
section_write16(sect, 0);
return creator_len;
}
static uint16_t write_symbolinfo_symbols(struct coff_Section *sect)
{
uint16_t len = 0, field_len;
uint32_t field_base;
struct cv8_symbol *sym;
saa_rewind(cv8_state.symbols);
while ((sym = saa_rstruct(cv8_state.symbols))) {
switch (sym->type) {
case SYMTYPE_LDATA:
case SYMTYPE_GDATA:
field_len = 12 + strlen(sym->name) + 1;
len += field_len - 2;
section_write16(sect, field_len);
if (sym->type == SYMTYPE_LDATA)
section_write16(sect, 0x110C);
else
section_write16(sect, 0x110D);
section_write32(sect, sym->symtype);
field_base = sect->len;
section_write32(sect, 0); /* SECREL */
section_write16(sect, 0); /* SECTION */
break;
case SYMTYPE_PROC:
case SYMTYPE_CODE:
field_len = 9 + strlen(sym->name) + 1;
len += field_len - 2;
section_write16(sect, field_len);
section_write16(sect, 0x1105);
field_base = sect->len;
section_write32(sect, 0); /* SECREL */
section_write16(sect, 0); /* SECTION */
section_write8(sect, 0); /* FLAG */
break;
default:
nasm_assert(!"unknown symbol type");
}
section_wbytes(sect, sym->name, strlen(sym->name) + 1);
register_reloc(sect, sym->name, field_base,
win64 ? IMAGE_REL_AMD64_SECREL :
IMAGE_REL_I386_SECREL);
register_reloc(sect, sym->name, field_base + 4,
win64 ? IMAGE_REL_AMD64_SECTION :
IMAGE_REL_I386_SECTION);
}
return len;
}
static void write_symbolinfo_table(struct coff_Section *const sect)
{
static const char creator_str[] = "The Netwide Assembler " NASM_VER;
uint16_t obj_length, creator_length, sym_length;
uint32_t field_length = 0, out_len;
nasm_assert(cv8_state.outfile.namebytes);
/* signature, language, outfile NULL */
obj_length = 2 + 4 + cv8_state.outfile.namebytes;
creator_length = 2 + 4 + 2 + 3*2 + 3*2 + strlen(creator_str)+1 + 2;
sym_length = ( cv8_state.num_syms[SYMTYPE_CODE] * 7) +
( cv8_state.num_syms[SYMTYPE_PROC] * 7) +
( cv8_state.num_syms[SYMTYPE_LDATA] * 10) +
( cv8_state.num_syms[SYMTYPE_GDATA] * 10) +
cv8_state.symbol_lengths;
field_length = 2 + obj_length +
2 + creator_length +
(4 * cv8_state.total_syms) + sym_length;
section_write32(sect, 0x000000F1);
section_write32(sect, field_length);
/* for sub fields, length preceeds type */
out_len = write_symbolinfo_obj(sect);
nasm_assert(out_len == obj_length);
out_len = write_symbolinfo_properties(sect, creator_str);
nasm_assert(out_len == creator_length);
out_len = write_symbolinfo_symbols(sect);
nasm_assert(out_len == sym_length);
}
static inline void align4_table(struct coff_Section *const sect)
{
unsigned diff;
uint32_t zero = 0;
struct SAA *data = sect->data;
if (data->wptr % 4 == 0)
return;
diff = 4 - (data->wptr % 4);
if (diff)
section_wbytes(sect, &zero, diff);
}
static void build_symbol_table(struct coff_Section *const sect)
{
section_write32(sect, 0x00000004);
write_filename_table(sect);
align4_table(sect);
write_sourcefile_table(sect);
align4_table(sect);
write_linenumber_table(sect);
align4_table(sect);
write_symbolinfo_table(sect);
align4_table(sect);
}
static void build_type_table(struct coff_Section *const sect)
{
uint16_t field_len;
struct cv8_symbol *sym;
section_write32(sect, 0x00000004);
saa_rewind(cv8_state.symbols);
while ((sym = saa_rstruct(cv8_state.symbols))) {
if (sym->type != SYMTYPE_PROC)
continue;
/* proc leaf */
field_len = 2 + 4 + 4 + 4 + 2;
section_write16(sect, field_len);
section_write16(sect, 0x1008); /* PROC type */
section_write32(sect, 0x00000003); /* return type */
section_write32(sect, 0); /* calling convention (default) */
section_write32(sect, sym->typeindex);
section_write16(sect, 0); /* # params */
/* arglist */
field_len = 2 + 4;
section_write16(sect, field_len);
section_write16(sect, 0x1201); /* ARGLIST */
section_write32(sect, 0); /*num params */
}
}