/* 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++; } /* Output a compressed CodeView integer. The return value is the number of bytes used. */ unsigned int output_cv_comp (char *p, offsetT value, int sign) { char *orig = p; if (sign) { if (value < -0xfffffff || value > 0xfffffff) { as_bad (_("value cannot be expressed as a .cv_scomp")); return 0; } } else { if (value < 0 || value > 0x1fffffff) { as_bad (_("value cannot be expressed as a .cv_ucomp")); return 0; } } if (sign) { if (value >= 0) value <<= 1; else value = (-value << 1) | 1; } if (value <= 0x7f) { *p++ = value; } else if (value <= 0x3fff) { *p++ = 0x80 | (value >> 8); *p++ = value & 0xff; } else { *p++ = 0xc0 | (value >> 24); *p++ = (value >> 16) & 0xff; *p++ = (value >> 8) & 0xff; *p++ = value & 0xff; } return p - orig; } /* Return the size needed to output a compressed CodeView integer. */ unsigned int sizeof_cv_comp (offsetT value, int sign) { if (sign) { if (value < -0xfffffff || value > 0xfffffff) return 0; if (value >= 0) value <<= 1; else value = (-value << 1) | 1; } else { if (value > 0x1fffffff) return 0; } if (value <= 0x7f) return 1; else if (value <= 0x3fff) return 2; else if (value <= 0x1fffffff) return 4; else return 0; } #else void codeview_finish (void) { } void codeview_generate_asm_lineno (void) { } #endif /* TE_PE && O_secrel */