blob: 3eaa7a64fd2dc70c9be983941744a685b788a1d2 [file] [log] [blame]
/* codeview.c - CodeView debug support
Copyright (C) 2022-2024 Free Software Foundation, Inc.
This file is part of GAS, the GNU Assembler.
GAS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GAS 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with GAS; see the file COPYING. If not, write to the Free
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
02110-1301, USA. */
#include "as.h"
#include "codeview.h"
#include "subsegs.h"
#include "filenames.h"
#include "md5.h"
#if defined (TE_PE) && defined (O_secrel)
#define NUM_MD5_BYTES 16
#define FILE_ENTRY_PADDING 2
#define FILE_ENTRY_LENGTH (sizeof (struct file_checksum) + NUM_MD5_BYTES \
+ FILE_ENTRY_PADDING)
struct line
{
struct line *next;
unsigned int lineno;
addressT frag_offset;
};
struct line_file
{
struct line_file *next;
unsigned int fileno;
struct line *lines_head, *lines_tail;
unsigned int num_lines;
};
struct line_block
{
struct line_block *next;
segT seg;
unsigned int subseg;
fragS *frag;
symbolS *sym;
struct line_file *files_head, *files_tail;
};
struct source_file
{
struct source_file *next;
unsigned int num;
char *filename;
uint32_t string_pos;
uint8_t md5[NUM_MD5_BYTES];
};
static struct line_block *blocks_head = NULL, *blocks_tail = NULL;
static struct source_file *files_head = NULL, *files_tail = NULL;
static unsigned int num_source_files = 0;
/* Return the size of the current fragment (taken from dwarf2dbg.c). */
static offsetT
get_frag_fix (fragS *frag, segT seg)
{
frchainS *fr;
if (frag->fr_next)
return frag->fr_fix;
for (fr = seg_info (seg)->frchainP; fr; fr = fr->frch_next)
if (fr->frch_last == frag)
return (char *) obstack_next_free (&fr->frch_obstack) - frag->fr_literal;
abort ();
}
/* Emit a .secrel32 relocation. */
static void
emit_secrel32_reloc (symbolS *sym)
{
expressionS exp;
memset (&exp, 0, sizeof (exp));
exp.X_op = O_secrel;
exp.X_add_symbol = sym;
exp.X_add_number = 0;
emit_expr (&exp, sizeof (uint32_t));
}
/* Emit a .secidx relocation. */
static void
emit_secidx_reloc (symbolS *sym)
{
expressionS exp;
memset (&exp, 0, sizeof (exp));
exp.X_op = O_secidx;
exp.X_add_symbol = sym;
exp.X_add_number = 0;
emit_expr (&exp, sizeof (uint16_t));
}
/* Write the DEBUG_S_STRINGTABLE subsection. */
static void
write_string_table (void)
{
uint32_t len;
unsigned int padding;
char *ptr, *start;
len = 1;
for (struct source_file *sf = files_head; sf; sf = sf->next)
{
len += strlen (sf->filename) + 1;
}
if (len % 4)
padding = 4 - (len % 4);
else
padding = 0;
ptr = frag_more (sizeof (uint32_t) + sizeof (uint32_t) + len + padding);
bfd_putl32 (DEBUG_S_STRINGTABLE, ptr);
ptr += sizeof (uint32_t);
bfd_putl32 (len, ptr);
ptr += sizeof (uint32_t);
start = ptr;
*ptr = 0;
ptr++;
for (struct source_file *sf = files_head; sf; sf = sf->next)
{
size_t fn_len = strlen (sf->filename);
sf->string_pos = ptr - start;
memcpy(ptr, sf->filename, fn_len + 1);
ptr += fn_len + 1;
}
memset (ptr, 0, padding);
}
/* Write the DEBUG_S_FILECHKSMS subsection. */
static void
write_checksums (void)
{
uint32_t len;
char *ptr;
len = FILE_ENTRY_LENGTH * num_source_files;
ptr = frag_more (sizeof (uint32_t) + sizeof (uint32_t) + len);
bfd_putl32 (DEBUG_S_FILECHKSMS, ptr);
ptr += sizeof (uint32_t);
bfd_putl32 (len, ptr);
ptr += sizeof (uint32_t);
for (struct source_file *sf = files_head; sf; sf = sf->next)
{
struct file_checksum fc;
fc.file_id = sf->string_pos;
fc.checksum_length = NUM_MD5_BYTES;
fc.checksum_type = CHKSUM_TYPE_MD5;
memcpy (ptr, &fc, sizeof (struct file_checksum));
ptr += sizeof (struct file_checksum);
memcpy (ptr, sf->md5, NUM_MD5_BYTES);
ptr += NUM_MD5_BYTES;
memset (ptr, 0, FILE_ENTRY_PADDING);
ptr += FILE_ENTRY_PADDING;
}
}
/* Write the DEBUG_S_LINES subsection. */
static void
write_lines_info (void)
{
while (blocks_head)
{
struct line_block *lb;
struct line_file *lf;
uint32_t len;
uint32_t off;
char *ptr;
lb = blocks_head;
bfd_putl32 (DEBUG_S_LINES, frag_more (sizeof (uint32_t)));
len = sizeof (struct cv_lines_header);
for (lf = lb->files_head; lf; lf = lf->next)
{
len += sizeof (struct cv_lines_block);
len += sizeof (struct cv_line) * lf->num_lines;
}
bfd_putl32 (len, frag_more (sizeof (uint32_t)));
/* Write the header (struct cv_lines_header). We can't use a struct
for this as we're also emitting relocations. */
emit_secrel32_reloc (lb->sym);
emit_secidx_reloc (lb->sym);
ptr = frag_more (len - sizeof (uint32_t) - sizeof (uint16_t));
/* Flags */
bfd_putl16 (0, ptr);
ptr += sizeof (uint16_t);
off = lb->files_head->lines_head->frag_offset;
/* Length of region */
bfd_putl32 (get_frag_fix (lb->frag, lb->seg) - off, ptr);
ptr += sizeof (uint32_t);
while (lb->files_head)
{
struct cv_lines_block *block = (struct cv_lines_block *) ptr;
lf = lb->files_head;
bfd_putl32(lf->fileno * FILE_ENTRY_LENGTH, &block->file_id);
bfd_putl32(lf->num_lines, &block->num_lines);
bfd_putl32(sizeof (struct cv_lines_block)
+ (sizeof (struct cv_line) * lf->num_lines),
&block->length);
ptr += sizeof (struct cv_lines_block);
while (lf->lines_head)
{
struct line *l;
struct cv_line *l2 = (struct cv_line *) ptr;
l = lf->lines_head;
/* Only the bottom 24 bits of line_no actually encode the
line number. The top bit is a flag meaning "is
a statement". */
bfd_putl32 (l->frag_offset - off, &l2->offset);
bfd_putl32 (0x80000000 | (l->lineno & 0xffffff),
&l2->line_no);
lf->lines_head = l->next;
free(l);
ptr += sizeof (struct cv_line);
}
lb->files_head = lf->next;
free (lf);
}
blocks_head = lb->next;
free (lb);
}
}
/* Return the CodeView constant for the selected architecture. */
static uint16_t
target_processor (void)
{
switch (stdoutput->arch_info->arch)
{
case bfd_arch_i386:
if (stdoutput->arch_info->mach & bfd_mach_x86_64)
return CV_CFL_X64;
else
return CV_CFL_80386;
case bfd_arch_aarch64:
return CV_CFL_ARM64;
default:
return 0;
}
}
/* Write the CodeView symbols, describing the object name and
assembler version. */
static void
write_symbols_info (void)
{
static const char assembler[] = "GNU AS " VERSION;
char *path = lrealpath (out_file_name);
char *path2 = remap_debug_filename (path);
size_t path_len, padding;
uint32_t len;
struct OBJNAMESYM objname;
struct COMPILESYM3 compile3;
char *ptr;
free (path);
path = path2;
path_len = strlen (path);
len = sizeof (struct OBJNAMESYM) + path_len + 1;
len += sizeof (struct COMPILESYM3) + sizeof (assembler);
if (len % 4)
padding = 4 - (len % 4);
else
padding = 0;
len += padding;
ptr = frag_more (sizeof (uint32_t) + sizeof (uint32_t) + len);
bfd_putl32 (DEBUG_S_SYMBOLS, ptr);
ptr += sizeof (uint32_t);
bfd_putl32 (len, ptr);
ptr += sizeof (uint32_t);
/* Write S_OBJNAME entry. */
bfd_putl16 (sizeof (struct OBJNAMESYM) - sizeof (uint16_t) + path_len + 1,
&objname.length);
bfd_putl16 (S_OBJNAME, &objname.type);
bfd_putl32 (0, &objname.signature);
memcpy (ptr, &objname, sizeof (struct OBJNAMESYM));
ptr += sizeof (struct OBJNAMESYM);
memcpy (ptr, path, path_len + 1);
ptr += path_len + 1;
free (path);
/* Write S_COMPILE3 entry. */
bfd_putl16 (sizeof (struct COMPILESYM3) - sizeof (uint16_t)
+ sizeof (assembler) + padding, &compile3.length);
bfd_putl16 (S_COMPILE3, &compile3.type);
bfd_putl32 (CV_CFL_MASM, &compile3.flags);
bfd_putl16 (target_processor (), &compile3.machine);
bfd_putl16 (0, &compile3.frontend_major);
bfd_putl16 (0, &compile3.frontend_minor);
bfd_putl16 (0, &compile3.frontend_build);
bfd_putl16 (0, &compile3.frontend_qfe);
bfd_putl16 (0, &compile3.backend_major);
bfd_putl16 (0, &compile3.backend_minor);
bfd_putl16 (0, &compile3.backend_build);
bfd_putl16 (0, &compile3.backend_qfe);
memcpy (ptr, &compile3, sizeof (struct COMPILESYM3));
ptr += sizeof (struct COMPILESYM3);
memcpy (ptr, assembler, sizeof (assembler));
ptr += sizeof (assembler);
memset (ptr, 0, padding);
}
/* Processing of the file has finished, emit the .debug$S section. */
void
codeview_finish (void)
{
segT seg;
if (!blocks_head)
return;
seg = subseg_new (".debug$S", 0);
bfd_set_section_flags (seg, SEC_READONLY | SEC_NEVER_LOAD);
bfd_putl32 (CV_SIGNATURE_C13, frag_more (sizeof (uint32_t)));
write_string_table ();
write_checksums ();
write_lines_info ();
write_symbols_info ();
}
/* Assign a new index number for the given file, or return the existing
one if already assigned. */
static unsigned int
get_fileno (const char *file)
{
struct source_file *sf;
char *path = lrealpath (file);
char *path2 = remap_debug_filename (path);
size_t path_len;
FILE *f;
free (path);
path = path2;
path_len = strlen (path);
for (sf = files_head; sf; sf = sf->next)
{
if (path_len == strlen (sf->filename)
&& !filename_ncmp (sf->filename, path, path_len))
{
free (path);
return sf->num;
}
}
sf = xmalloc (sizeof (struct source_file));
sf->next = NULL;
sf->num = num_source_files;
sf->filename = path;
f = fopen (file, "r");
if (!f)
as_fatal (_("could not open %s for reading"), file);
if (md5_stream (f, sf->md5))
{
fclose(f);
as_fatal (_("md5_stream failed"));
}
fclose(f);
if (!files_head)
files_head = sf;
else
files_tail->next = sf;
files_tail = sf;
num_source_files++;
return num_source_files - 1;
}
/* Called for each new line in asm file. */
void
codeview_generate_asm_lineno (void)
{
const char *file;
unsigned int filenr;
unsigned int lineno;
struct line *l;
symbolS *sym = NULL;
struct line_block *lb;
struct line_file *lf;
file = as_where (&lineno);
filenr = get_fileno (file);
if (!blocks_tail || blocks_tail->frag != frag_now)
{
static int label_num = 0;
char name[32];
sprintf (name, ".Loc.%u", label_num);
label_num++;
sym = symbol_new (name, now_seg, frag_now, frag_now_fix ());
lb = xmalloc (sizeof (struct line_block));
lb->next = NULL;
lb->seg = now_seg;
lb->subseg = now_subseg;
lb->frag = frag_now;
lb->sym = sym;
lb->files_head = lb->files_tail = NULL;
if (!blocks_head)
blocks_head = lb;
else
blocks_tail->next = lb;
blocks_tail = lb;
}
else
{
lb = blocks_tail;
}
if (!lb->files_tail || lb->files_tail->fileno != filenr)
{
lf = xmalloc (sizeof (struct line_file));
lf->next = NULL;
lf->fileno = filenr;
lf->lines_head = lf->lines_tail = NULL;
lf->num_lines = 0;
if (!lb->files_head)
lb->files_head = lf;
else
lb->files_tail->next = lf;
lb->files_tail = lf;
}
else
{
lf = lb->files_tail;
}
l = xmalloc (sizeof (struct line));
l->next = NULL;
l->lineno = lineno;
l->frag_offset = frag_now_fix ();
if (!lf->lines_head)
lf->lines_head = l;
else
lf->lines_tail->next = l;
lf->lines_tail = l;
lf->num_lines++;
}
#else
void
codeview_finish (void)
{
}
void
codeview_generate_asm_lineno (void)
{
}
#endif /* TE_PE && O_secrel */