Issue 260921.1: Signedness of DW_FORM_data values
| Author: | Simon Marchi |
|---|---|
| Champion: | |
| Date submitted: | 2026-09-21 |
| Date revised: | |
| Date closed: | |
| Type: | Enhancement |
| Status: | Open |
| DWARF version: | 6 |
Background
Given an attribute of form DW_FORM_data{1,2,4,8,16}, it is up to the consumer
to figure out what the bytes mean. In particular, given an attribute where an
integer is expected, the consumer must infer from the context whether the bytes
encode a signed or unsigned integer. This is important if the provided value
is smaller than the declared type (such as for a DW_TAG_variable with a
DW_AT_const_value), as the consumer must decide if it should sign-extend or
zero-extend the value. It is also important for attributes that do not have a
declared type, like DW_AT_decimal_scale, simply to know how to interpret the
value.
The problem is that "the context" is not defined. Different producers and consumers could independently reach different conclusions about when an attribute should be interpreted as signed or unsigned.
The spec says this about the DW_FORM_data<n> forms:
The data in
DW_FORM_data1,DW_FORM_data2,DW_FORM_data4,DW_FORM_data8andDW_FORM_data16can be anything. Depending on context, it may be a signed integer, an unsigned integer, a floating-point constant, or anything else. A consumer must use context to know how to interpret the bits, which if they are target machine data (such as an integer or floating-point constant) will be in target machine byte order.
Then there is this non-normative text:
If one of the
DW_FORM_data<n>forms is used to represent a signed or unsigned integer, it can be hard for a consumer to discover the context necessary to determine which interpretation is intended. Producers are therefore strongly encouraged to useDW_FORM_sdataorDW_FORM_udatafor signed and unsigned integers respectively, rather thanDW_FORM_data<n>.
I don't think that saying this in non-normative text is helpful. The reality
is that producers do use the DW_FORM_data<n> forms for integers, so the
consumers still have to know how to deal with it.
For example, given this:
0x00000037: DW_TAG_enumeration_type
DW_AT_name [DW_FORM_string] ("E")
DW_AT_encoding [DW_FORM_data1] (DW_ATE_signed)
DW_AT_byte_size [DW_FORM_data1] (0x04)
DW_AT_type [DW_FORM_ref4] (0x00000050 "int")
0x00000047: DW_TAG_enumerator
DW_AT_name [DW_FORM_string] ("X")
DW_AT_const_value [DW_FORM_data1] (0xff)
Does 0xff mean 255 or -1?
And here?
0x00000037: DW_TAG_enumeration_type
DW_AT_name [DW_FORM_string] ("E")
DW_AT_encoding [DW_FORM_data1] (DW_ATE_unsigned)
DW_AT_byte_size [DW_FORM_data1] (0x04)
DW_AT_type [DW_FORM_ref4] (0x00000050 "unsigned int")
0x00000047: DW_TAG_enumerator
DW_AT_name [DW_FORM_string] ("X")
DW_AT_const_value [DW_FORM_data1] (0xff)
Some people's intuition is that because the first enumeration has a signed type, then the const value should be sign-extended, unlike the second enumeration whose value should be zero-extended. Other people think that both values should always be zero-extended.
Then, there are some attributes like DW_AT_decimal_scale that don't even have
an associated type, and can accept negative values. If you see a
DW_AT_decimal_scale attribute with a "DW_FORM_data1 0xff" value, is it 255 or
-1? I can't see what context a consumer could use to determine that.
There are two attributes that further specify the signedness of the value.
DW_AT_discr_value says:
The value that selects a given variant may be represented in one of three ways. The variant entry may have a
DW_AT_discr_valueattribute whose value represents the discriminant value selecting this variant. The number is signed if the tag type for the variant part containing this variant is a signed type. The number is unsigned if the tag type is an unsigned type.
While DW_AT_bias says:
If the
DW_AT_biasis encoded usingDW_FORM_data<n>, then the bias value is treated as an unsigned integer.
History
The first time this problem was brought up was in issue 020702.1.
The outcome of this issue was to add the paragraphs quoted above. So the issue
was "it's not clear how to interpret DW_FORM_data<n> integer values", and the
outcome was to write in the standard "it's indeed not clear how to interpret
DW_FORM_data<n> integer values". I don't think this really helped.
Then there was this thread on dwarf-discuss.
This thread did not reach any particularly useful conclusion, but showed that
people had different intuitions of how DW_FORM_data<n> should work.
Finally, there was this recent gcc bug, which shows that there is still some confusion about this.
The current state of things for gcc and LLVM (the only two producers I have
access to) is that they'll generally use the DW_FORM_data<n> forms to represent
non-negative numbers, and resort to DW_FORM_sdata for negative numbers. So,
when applied to an integer context, they assume that the consumers will treat
the DW_FORM_data<n> content as an unsigned integer (except for
DW_AT_discr_value, because of how it's specified) and zero-extend the value if
needed.
Proposal
Here are the high level ideas of this proposal (see below for the formal proposed changes).
-
Define the
DW_FORM_data<n>forms such that when they are used to provide an integer value, they encode an unsigned integer. TheDW_FORM_data<n>can still be used to provide non-integer data. -
Add new
DW_FORM_sdata{1,2,4,8,16}forms, which are defined to encode a signed integer. -
Remove the part about
DW_AT_discr_valuethat specifies that the value is unsigned or signed depending on the type of the discriminant.The signedness of the LEB values in
DW_AT_discr_listwould still depend on the discriminant type's signedness. If we want to avoid this, we could introduce signed and unsigned versions ofDW_DSC_labelandDW_DSC_range. This would make it entirely possible to interpret the values without having to peek at another DIE. -
Remove the sentence in the
DW_AT_biasdescription saying that aDW_FORM_data<n>bias value is treated as an unsigned integer.
Impact on producers:
-
Producers that use
DW_FORM_data<n>to convey non-negative values don't need to change anything. -
Producers that use
DW_FORM_data<n>to convey negative values (including forDW_AT_discr_value) would need to switch to the correspondingDW_FORM_sdata<n>form. -
In both cases, they don't need to change the data bytes they emit, just the form.
Impact on consumers:
- The
DW_AT_discr_valuechange would be a breaking change, likely requiring a DWARF version check in the consumers. However, the result is a uniform behavior ofDW_FORM_data<n>across all attributes, so I think it's worth it.
While not the primary goal, the introduction of the DW_FORM_sdata<n> forms will
allow expressing negative numbers in a slightly more compact way than with
DW_FORM_sdata, without any ambiguity.
Technically, some attributes with integer values could use the block forms
(e.g. DW_AT_const_value), which would present the same ambiguity as the
constant forms. I did not touch those, as I am not aware of a producer using
those forms to encode integers, but we could change them in a similar way if we
wanted to be thorough.
Alternatives
An alternative I considered is to get rid of DW_FORM_data<n>, and just use
DW_FORM_{s,u}data for everything. This one would make the standard simpler and
more uniform, which would be nice! But at the cost of making the debug info
slightly larger, which is why I don't expect this option to be very popular. I
modified gcc to do this, and it made the debug info of my test program (gdb)
0.66% larger. I am open to going this direction if people like it.
Proposed changes
The changes below are relative to the current DWARF Version 6 draft (dwarf-spec git commit ab16b53c56, "Incorporate changes from DWARF September 14, 2026 meeting.").
-
In Section 2.2 ("Attribute Types"), Table 2.3 ("Classes of attribute value"), in the row for the constant class, remove the word "uninterpreted", resulting in:
One, two, four, eight or sixteen bytes of data, or data encoded in the SLEB or ULEB variable length format (see Section 8.6).
-
In Section 6.1.1.1 ("Simple Encodings"), remove this text:
If the
DW_AT_biasis encoded usingDW_FORM_data<n>, then the bias value is treated as an unsigned integer. -
In Section 6.7.11 ("Variant Entries"), in the paragraph describing DW_AT_discr_value, remove this text:
The number is signed if the tag type for the variant part containing this variant is a signed type. The number is unsigned if the tag type is an unsigned type.
-
In Section 6.7.11 ("Variant Entries"), in the paragraph describing DW_AT_discr_list, remove this text:
as defined above for the
DW_AT_discr_valueattributeRemove this text:
Both values follow the rules for signedness just described.
and add this text in its place:
Values are signed if the tag type for the variant part containing this variant is a signed type, and unsigned if the tag type is an unsigned type.
-
In Section 7.2.4.2 ("Producer-defined Content Descriptions"), in the list of forms that a producer-defined content type code may be combined with, add
DW_FORM_sdata1,DW_FORM_sdata2,DW_FORM_sdata4,DW_FORM_sdata8andDW_FORM_sdata16. -
In Section 7.3.1 ("Macro Information Header"), in the list of forms allowed for the operands of a producer extension entry opcode, add
DW_FORM_sdata1,DW_FORM_sdata2,DW_FORM_sdata4,DW_FORM_sdata8andDW_FORM_sdata16. -
In Section 8.5.5 ("Classes and Forms"), in the description of the constant class, remove this text:
There are eight forms of constants. There are fixed length constant data forms for one-, two-, four-, eight- and sixteen-byte values (respectively,
DW_FORM_data1,DW_FORM_data2,DW_FORM_data4,DW_FORM_data8andDW_FORM_data16).and add this text in its place:
There are thirteen forms of constants. There are fixed length constant data forms for one-, two-, four-, eight- and sixteen-byte values (respectively,
DW_FORM_data1,DW_FORM_data2,DW_FORM_data4,DW_FORM_data8andDW_FORM_data16), and signed fixed length constant data forms for the same sizes (respectively,DW_FORM_sdata1,DW_FORM_sdata2,DW_FORM_sdata4,DW_FORM_sdata8andDW_FORM_sdata16).Remove this text:
The data in
DW_FORM_data1,DW_FORM_data2,DW_FORM_data4,DW_FORM_data8andDW_FORM_data16can be anything. Depending on context, it may be a signed integer, an unsigned integer, a floating-point constant, or anything else. A consumer must use context to know how to interpret the bits, which if they are target machine data (such as an integer or floating-point constant) will be in target machine byte order.[non-normative] If one of the
DW_FORM_data<n>forms is used to represent a signed or unsigned integer, it can be hard for a consumer to discover the context necessary to determine which interpretation is intended. Producers are therefore strongly encouraged to useDW_FORM_sdataorDW_FORM_udatafor signed and unsigned integers respectively, rather thanDW_FORM_data<n>.and add this text in its place:
The data in
DW_FORM_data<n>can be anything. Depending on context, it may be an unsigned integer, a floating-point constant, or anything else. A consumer must use context to know how to interpret the bits, which if they are target machine data (such as an integer or floating-point constant) will be in target machine byte order. Where the context calls for an integer, the data is interpreted as an unsigned integer; if the value is used at a width larger than the form, it is zero-extended.The data in
DW_FORM_sdata<n>is a signed integer, in target machine byte order. If the value is used at a width larger than the form, it is sign-extended. -
In Section 8.5.6 ("Form Encodings"), add these rows to Table 8.6 ("Attribute form encodings"):
Form name Value Classes ------------------------------------- DW_FORM_sdata1 0x30 constant DW_FORM_sdata2 0x31 constant DW_FORM_sdata4 0x32 constant DW_FORM_sdata8 0x33 constant DW_FORM_sdata16 0x34 constant