| /* 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 */ |