From patchwork Tue Nov 12 01:40:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Harmstone X-Patchwork-Id: 100838 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 75BBF3858C98 for ; Tue, 12 Nov 2024 01:42:55 +0000 (GMT) X-Original-To: binutils@sourceware.org Delivered-To: binutils@sourceware.org Received: from mail.burntcomma.com (mail2.burntcomma.com [217.169.27.34]) by sourceware.org (Postfix) with ESMTPS id 238163858C56 for ; Tue, 12 Nov 2024 01:40:23 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 238163858C56 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=harmstone.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=harmstone.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 238163858C56 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=217.169.27.34 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1731375633; cv=none; b=CFO7fNlbSyHqAljY/GvGXjrVQ1dYJkBrblOoxVhnMN0d7OqcxhhfjVBpykgFhlmbIFvMIqa+M9kG9ZmqiTTdPaS03wZfsW+/b0kcW+d32TANMlyQe6/gUk6KaJdFIs875nqLtIja39A/qDJDujuf5NtSWmgTDxoK5Sss7ewGZ3k= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1731375633; c=relaxed/simple; bh=jleyVCNuDl0pacqjYu6gqHmeovteiRllL0am78Ed6W4=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:Mime-Version; b=UbRT9L4fGutv+4pt+s/Le0FwdxyHzf0/lvfoEEWyweaspWZ107n/UKxaz9T63iuc6YPCjSDazZvgA7XPylnF2VcbpJMeKsEMNw1yshRsnM5pgwdoXss5v0HM4rLG4Biy0j11mh+Gg0gxg8AEoy1oqezL0pkWpKKoJH01FN01dJI= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from localhost.localdomain (beren.burntcomma.com [IPv6:2a02:8012:8cf0:0:ce28:aaff:fe0d:6db2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (Client did not present a certificate) by mail.burntcomma.com (Postfix) with ESMTPSA id 40B311F5252; Tue, 12 Nov 2024 01:40:18 +0000 (GMT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=harmstone.com; s=mail; t=1731375618; bh=I+xhW92H+Zz0p5PAxKHCGnjFtX/TgRtdULQDDFJDfCA=; h=From:To:Cc:Subject:Date; b=O7kdZRPvlwr4mxbCMgsmTOPQ4ZwEEmj4zE+tVfITP6sVzIQXbr17LpzPLrPmLKyYb Q8IyFVgaqNgHxHKAvQys6ssULN5/EZ4apyQSx1IgwFV3yvnFOLNcR++k2Kh+cAj4A7 ycaVqP+6EQKjqHd2N90pXHVRBvpSOWso8M7talCw= From: Mark Harmstone To: binutils@sourceware.org Cc: Mark Harmstone Subject: [PATCH v3] gas: add .cv_ucomp and .cv_scomp pseudo-directives Date: Tue, 12 Nov 2024 01:40:04 +0000 Message-ID: <20241112014009.32623-1-mark@harmstone.com> Mime-Version: 1.0 X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: binutils@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Binutils mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: binutils-bounces~patchwork=sourceware.org@sourceware.org Add .cv_ucomp and .cv_scomp pseudo-directives for object files for Windows targets, which encode compressed CodeView integers according to the algorithm in CVCompressData in https://github.com/Microsoft/microsoft-pdb/blob/master/include/cvinfo.h. This is essentially Microsoft's answer to the LEB128, though used in far fewer places. CodeView uses these to encode the "binary annotations" in the S_INLINESITE symbol, which express the relationship between code offsets and line numbers in inlined functions. This has to be done in the assembler as GCC doesn't know how many bytes each instruction takes up. There's no equivalent for this for MSVC or LLVM, as in both cases the assembler and compiler are integrated. .cv_ucomp represents an unsigned big-endian integer between 0 and 0x1fffffff, taking up 1, 2, or 4 bytes: Value between 0 and 0x7f: 0aaaaaaa -> 0aaaaaaa (identity-mapped) Value between 0x80 and 0x3fff: 00aaaaaa bbbbbbbb -> 10aaaaaa bbbbbbbb Value between 0x4000 and 0x1fffffff: 000aaaaa bbbbbbbb ccccccccc dddddddd -> 110aaaaa bbbbbbbb ccccccccc dddddddd .cv_scomp represents a signed big-endian integer between -0xfffffff and 0xfffffff, encoded according to EncodeSignedInt32 in cvinfo.h. The absolute value of the integer is shifted left one bit, the LSB set for a negative value, and the result expressed as if it were a .cv_ucomp: cv_scomp(x) = cv_ucomp((abs(x) << 1) | (x < 0 ? 1 : 0)) --- Changelog: v3: * Moved output_cv_comp and sizeof_cv_comp to gas/codeview.c * Gated .cv_ucomp and .cv_scomp behind TE_PE && O_secrel, to match existing CodeView code * Fixed cv_comp test on aarch64 * Misc. minor changes as per Jan's suggestions v2: * Handle O_subtract case when symbols are in different fragments, and add test for this gas/as.h | 5 +- gas/codeview.c | 78 ++++++++++++++++++++++++ gas/codeview.h | 2 + gas/read.c | 98 ++++++++++++++++++++++++++++++ gas/testsuite/gas/pe/cv_comp.d | 14 +++++ gas/testsuite/gas/pe/cv_comp.s | 105 +++++++++++++++++++++++++++++++++ gas/testsuite/gas/pe/pe.exp | 5 ++ gas/write.c | 44 ++++++++++++++ 8 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 gas/testsuite/gas/pe/cv_comp.d create mode 100644 gas/testsuite/gas/pe/cv_comp.s diff --git a/gas/as.h b/gas/as.h index 780773cd78c..3d5f710c5c5 100644 --- a/gas/as.h +++ b/gas/as.h @@ -263,7 +263,10 @@ enum _relax_state rs_dwarf2dbg, /* SFrame FRE type selection optimization. */ - rs_sframe + rs_sframe, + + /* CodeView compressed integer. */ + rs_cv_comp, }; typedef enum _relax_state relax_stateT; diff --git a/gas/codeview.c b/gas/codeview.c index 3eaa7a64fd2..445a3b97761 100644 --- a/gas/codeview.c +++ b/gas/codeview.c @@ -533,6 +533,84 @@ codeview_generate_asm_lineno (void) 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_fatal (_("value cannot be expressed as a .cv_scomp")); + } + else + { + if (value > 0x1fffffff) + as_fatal (_("value cannot be expressed as a .cv_ucomp")); + } + + 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 diff --git a/gas/codeview.h b/gas/codeview.h index 57f8dfaedbf..ef2e78a9477 100644 --- a/gas/codeview.h +++ b/gas/codeview.h @@ -101,5 +101,7 @@ struct cv_line extern void codeview_finish (void); extern void codeview_generate_asm_lineno (void); +extern unsigned int output_cv_comp (char *, offsetT, int); +extern unsigned int sizeof_cv_comp (offsetT, int); #endif diff --git a/gas/read.c b/gas/read.c index aefbd7aefe8..589c7b080c2 100644 --- a/gas/read.c +++ b/gas/read.c @@ -265,6 +265,9 @@ static void poend (void); static size_t get_macro_line_sb (sb *); static void generate_file_debug (void); static char *_find_end_of_line (char *, int, int, int); +#if defined (TE_PE) && defined (O_secrel) +static void s_cv_comp (int sign); +#endif void read_begin (void) @@ -369,6 +372,10 @@ static const pseudo_typeS potable[] = { {"comm", s_comm, 0}, {"common", s_mri_common, 0}, {"common.s", s_mri_common, 1}, +#if defined (TE_PE) && defined (O_secrel) + {"cv_scomp", s_cv_comp, 1}, + {"cv_ucomp", s_cv_comp, 0}, +#endif {"data", s_data, 0}, {"dc", cons, 2}, {"dc.a", cons, 0}, @@ -5457,6 +5464,97 @@ s_leb128 (int sign) demand_empty_rest_of_line (); } +#if defined (TE_PE) && defined (O_secrel) + +/* Generate the appropriate fragments for a given expression to emit a + cv_comp value. SIGN is 1 for cv_scomp, 0 for cv_ucomp. */ + +static void +emit_cv_comp_expr (expressionS *exp, int sign) +{ + operatorT op = exp->X_op; + + if (op == O_absent || op == O_illegal) + { + as_warn (_("zero assumed for missing expression")); + exp->X_add_number = 0; + op = O_constant; + } + else if (op == O_big) + { + as_bad (_("number invalid")); + exp->X_add_number = 0; + op = O_constant; + } + else if (op == O_register) + { + as_warn (_("register value used as expression")); + op = O_constant; + } + + if (now_seg == absolute_section) + { + if (op != O_constant || exp->X_add_number != 0) + as_bad (_("attempt to store value in absolute section")); + abs_section_offset++; + return; + } + + if ((op != O_constant || exp->X_add_number != 0) && in_bss ()) + as_bad (_("attempt to store non-zero value in section `%s'"), + segment_name (now_seg)); + + /* Let the backend know that subsequent data may be byte aligned. */ +#ifdef md_cons_align + md_cons_align (1); +#endif + + if (op == O_constant) + { + offsetT value = exp->X_add_number; + unsigned int size; + char *p; + + /* If we've got a constant, emit the thing directly right now. */ + + size = sizeof_cv_comp (value, sign); + p = frag_more (size); + if (output_cv_comp (p, value, sign) > size) + abort (); + } + else + { + /* Otherwise, we have to create a variable sized fragment and + resolve things later. */ + + frag_var (rs_cv_comp, 4, 0, sign, make_expr_symbol (exp), 0, NULL); + } +} + +/* Parse the .cv_ucomp and .cv_scomp pseudos. */ + +static void +s_cv_comp (int sign) +{ + expressionS exp; + +#ifdef md_flush_pending_output + md_flush_pending_output (); +#endif + + do + { + expression (&exp); + emit_cv_comp_expr (&exp, sign); + } + while (*input_line_pointer++ == ','); + + input_line_pointer--; + demand_empty_rest_of_line (); +} + +#endif /* TE_PE && O_secrel */ + /* Code for handling base64 encoded strings. Based upon code in sharutils' lib/base64.c source file, written by Simon Josefsson. Which was partially adapted from GNU MailUtils diff --git a/gas/testsuite/gas/pe/cv_comp.d b/gas/testsuite/gas/pe/cv_comp.d new file mode 100644 index 00000000000..3a87554c6a5 --- /dev/null +++ b/gas/testsuite/gas/pe/cv_comp.d @@ -0,0 +1,14 @@ +#objdump: -s -j .rdata +#name: CodeView compressed integer test + +.*: .* + +Contents of section .rdata: + 0000 21002101 212a217f 21808021 853921bf .* + 0010 ff21c000 400021c0 0f424021 dfffffff .* + 0020 21002102 21542180 fe218100 218a7221 .* + 0030 c0007ffe 21c00080 0021c01e 848021df .* + 0040 fffffe21 03215521 80ff2181 01218a73 .* + 0050 21c0007f ff21c000 800121c0 1e848121 .* + 0060 dfffffff 21022104 21900421 04210821 .* + 0070 a0082105 210921a0 0921.* diff --git a/gas/testsuite/gas/pe/cv_comp.s b/gas/testsuite/gas/pe/cv_comp.s new file mode 100644 index 00000000000..29183d5a346 --- /dev/null +++ b/gas/testsuite/gas/pe/cv_comp.s @@ -0,0 +1,105 @@ + .section .rdata + + .ascii "!" + .cv_ucomp 0 + .ascii "!" + .cv_ucomp 1 + .ascii "!" + .cv_ucomp 42 + .ascii "!" + .cv_ucomp 127 + .ascii "!" + .cv_ucomp 128 + .ascii "!" + .cv_ucomp 1337 + .ascii "!" + .cv_ucomp 16383 + .ascii "!" + .cv_ucomp 16384 + .ascii "!" + .cv_ucomp 1000000 + .ascii "!" + .cv_ucomp 536870911 + + .ascii "!" + .cv_scomp 0 + .ascii "!" + .cv_scomp 1 + .ascii "!" + .cv_scomp 42 + .ascii "!" + .cv_scomp 127 + .ascii "!" + .cv_scomp 128 + .ascii "!" + .cv_scomp 1337 + .ascii "!" + .cv_scomp 16383 + .ascii "!" + .cv_scomp 16384 + .ascii "!" + .cv_scomp 1000000 + .ascii "!" + .cv_scomp 268435455 + + .ascii "!" + .cv_scomp -1 + .ascii "!" + .cv_scomp -42 + .ascii "!" + .cv_scomp -127 + .ascii "!" + .cv_scomp -128 + .ascii "!" + .cv_scomp -1337 + .ascii "!" + .cv_scomp -16383 + .ascii "!" + .cv_scomp -16384 + .ascii "!" + .cv_scomp -1000000 + .ascii "!" + .cv_scomp -268435455 + + .ascii "!" + # 2 + .cv_ucomp addr2 - addr1 + .ascii "!" + # 4 + .cv_ucomp addr3 - addr1 + .ascii "!" + # 4100 + .cv_ucomp addr4 - addr1 + + .ascii "!" + # 2 + .cv_scomp addr2 - addr1 + .ascii "!" + # 4 + .cv_scomp addr3 - addr1 + .ascii "!" + # 4100 + .cv_scomp addr4 - addr1 + .ascii "!" + # -2 + .cv_scomp addr1 - addr2 + .ascii "!" + # -4 + .cv_scomp addr1 - addr3 + .ascii "!" + # -4100 + .cv_scomp addr1 - addr4 + .ascii "!" + + .data + .space 1 +addr1: # .data + 0x1 + .space 2 +addr2: # .data + 0x3 + .space 2 + # force new fragment + .text + .data +addr3: # .data + 0x5 + .space 0x1000 +addr4: # .data + 0x1005 diff --git a/gas/testsuite/gas/pe/pe.exp b/gas/testsuite/gas/pe/pe.exp index 5f8396e5924..b667fbfe73a 100644 --- a/gas/testsuite/gas/pe/pe.exp +++ b/gas/testsuite/gas/pe/pe.exp @@ -38,6 +38,11 @@ run_dump_test "section-exclude" run_dump_test "set" +if { [istarget "aarch64-*-*"] || [istarget "i*86-*-*"] + || [istarget "x86_64-*-*"] } then { + run_dump_test "cv_comp" +} + # SEH related tests # These tests are only for x86_64 targets diff --git a/gas/write.c b/gas/write.c index 853a9a012b7..a9c53d70f7f 100644 --- a/gas/write.c +++ b/gas/write.c @@ -26,6 +26,7 @@ #include "output-file.h" #include "dwarf2dbg.h" #include "compress-debug.h" +#include "codeview.h" #ifndef TC_FORCE_RELOCATION #define TC_FORCE_RELOCATION(FIX) \ @@ -513,6 +514,32 @@ cvt_frag_to_fill (segT sec ATTRIBUTE_UNUSED, fragS *fragP) break; #endif +#if defined (TE_PE) && defined (O_secrel) + case rs_cv_comp: + { + offsetT value = S_GET_VALUE (fragP->fr_symbol); + int size; + + if (!S_IS_DEFINED (fragP->fr_symbol)) + { + as_bad_where (fragP->fr_file, fragP->fr_line, + _(".cv_%ccomp operand is an undefined symbol: %s"), + fragP->fr_subtype ? 's' : 'u', + S_GET_NAME (fragP->fr_symbol)); + } + + size = output_cv_comp (fragP->fr_literal + fragP->fr_fix, value, + fragP->fr_subtype); + + fragP->fr_fix += size; + fragP->fr_type = rs_fill; + fragP->fr_var = 0; + fragP->fr_offset = 0; + fragP->fr_symbol = NULL; + } + break; +#endif + default: BAD_CASE (fragP->fr_type); break; @@ -2767,6 +2794,9 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass) #endif case rs_leb128: +#if defined (TE_PE) && defined (O_secrel) + case rs_cv_comp: +#endif /* Initial guess is always 1; doing otherwise can result in stable solutions that are larger than the minimum. */ address += fragP->fr_offset = 1; @@ -3120,6 +3150,20 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass) } break; +#if defined (TE_PE) && defined (O_secrel) + case rs_cv_comp: + { + valueT value; + offsetT size; + + value = resolve_symbol_value (fragP->fr_symbol); + size = sizeof_cv_comp (value, fragP->fr_subtype); + growth = size - fragP->fr_offset; + fragP->fr_offset = size; + } + break; +#endif + case rs_cfa: growth = eh_frame_relax_frag (fragP); break;