• Andrii Nakryiko's avatar
    dwarf_loader: Use DWARF recommended uniform bit offset scheme · c0fdc5e6
    Andrii Nakryiko authored
    Use uniform bit offset scheme as described in DWARF standard (though
    apparently not really followed by major compilers), in which bit offset
    is a natural extension of byte offset in both big- and little-endian
    architectures.
    
    BEFORE:
    
    1. Bit offsets for little-endian are output as offsets from highest-order bit
    of underlying int to highest-order bit of bitfield, so double-backwards for
    little-endian arch and counter to how byte offsets are used, which point to
    lowest-order bit of underlying type. This makes first bitfield to have bit
    offset 27, instead of natural 0.
    
    2. Bit offsets for big-endian are output as expected, by referencing
    highest-order bit offset from highest-order bit of underlying int. This is
    natural for big-endian platform, e.g., first bitfield has bit offset of 0.
    
    3. Big-endian target also has problem with determining bit holes, because bit
    positions have to be calculated differently for little- and big-endian
    platforms and previous commit changed pahole logic to follow little-endian
    semantics.
    
    4. BTF encoder outputs uniform bit offset for both little- and big-endian
    format (following DWARF's recommended bit offset scheme)
    
    5. BTF loader, though, follows DWARF loader's format and outputs little-endian
    bit offsets "double-backwards".
    
      $ gcc -g dwarf_test.c -o dwarf_test
      $ pahole -F dwarf dwarf_test
      struct S {
              int                        j:5;                  /*     0:27  4 */
              int                        k:6;                  /*     0:21  4 */
              int                        m:5;                  /*     0:16  4 */
              int                        n:8;                  /*     0: 8  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
      $ pahole -JV dwarf_test
      File dwarf_test:
      [1] STRUCT S kind_flag=1 size=4 vlen=4
              j type_id=2 bitfield_size=5 bits_offset=0
              k type_id=2 bitfield_size=6 bits_offset=5
              m type_id=2 bitfield_size=5 bits_offset=11
              n type_id=2 bitfield_size=8 bits_offset=16
      [2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
    
      $ pahole -F btf dwarf_test
      struct S {
              int                        j:5;                  /*     0:27  4 */
              int                        k:6;                  /*     0:21  4 */
              int                        m:5;                  /*     0:16  4 */
              int                        n:8;                  /*     0: 8  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
      $ aarch64-linux-gnu-gcc -mbig-endian -g -c dwarf_test.c -o dwarf_test.be
      $ pahole -F dwarf dwarf_test.be
      struct S {
    
              /* XXX 27 bits hole, try to pack */
    
              int                        j:5;                  /*     0: 0  4 */
    
              /* XXX 245 bits hole, try to pack */
    
              int                        k:6;                  /*     0: 5  4 */
    
              /* XXX 245 bits hole, try to pack */
    
              int                        m:5;                  /*     0:11  4 */
    
              /* XXX 243 bits hole, try to pack */
    
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit holes: 4, sum bit holes: 760 bits */
              /* bit_padding: 16 bits */
              /* last cacheline: 4 bytes */
    
              /* BRAIN FART ALERT! 4 bytes != 24 (member bits) + 0 (byte holes) + 760 (bit holes), diff = -768 bits */
      };
    
      $ pahole -JV dwarf_test.be
      File dwarf_test.be:
      [1] STRUCT S kind_flag=1 size=4 vlen=4
              j type_id=2 bitfield_size=5 bits_offset=0
              k type_id=2 bitfield_size=6 bits_offset=5
              m type_id=2 bitfield_size=5 bits_offset=11
              n type_id=2 bitfield_size=8 bits_offset=16
      [2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
    
      $ pahole -F btf dwarf_test.be
      struct S {
    
              /* XXX 27 bits hole, try to pack */
    
              int                        j:5;                  /*     0: 0  4 */
    
              /* XXX 245 bits hole, try to pack */
    
              int                        k:6;                  /*     0: 5  4 */
    
              /* XXX 245 bits hole, try to pack */
    
              int                        m:5;                  /*     0:11  4 */
    
              /* XXX 243 bits hole, try to pack */
    
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit holes: 4, sum bit holes: 760 bits */
              /* bit_padding: 16 bits */
              /* last cacheline: 4 bytes */
    
              /* BRAIN FART ALERT! 4 bytes != 24 (member bits) + 0 (byte holes) + 760 (bit holes), diff = -768 bits */
      };
    
    AFTER:
    
    1. Same output for little- and big-endian binaries, both for BTF and DWARF
    loader.
    
    2. For little-endian target, bit offsets are natural extensions of byte offset,
    counting from lowest-order bit of underlying int to lowest-order bit of a
    bitfield.
    
    3. BTF encoder still emits correct and natural bit offsets (for both binaries).
    
    4. No more BRAIN FART ALERTs for big-endian.
    
      $ pahole -F dwarf dwarf_test
      struct S {
              int                        j:5;                  /*     0: 0  4 */
              int                        k:6;                  /*     0: 5  4 */
              int                        m:5;                  /*     0:11  4 */
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
      $ pahole -JV dwarf_test
      File dwarf_test:
      [1] STRUCT S kind_flag=1 size=4 vlen=4
              j type_id=2 bitfield_size=5 bits_offset=0
              k type_id=2 bitfield_size=6 bits_offset=5
              m type_id=2 bitfield_size=5 bits_offset=11
              n type_id=2 bitfield_size=8 bits_offset=16
      [2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
    
      $ pahole -F btf dwarf_test
      struct S {
              int                        j:5;                  /*     0: 0  4 */
              int                        k:6;                  /*     0: 5  4 */
              int                        m:5;                  /*     0:11  4 */
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
      $ pahole -F dwarf dwarf_test.be
      struct S {
              int                        j:5;                  /*     0: 0  4 */
              int                        k:6;                  /*     0: 5  4 */
              int                        m:5;                  /*     0:11  4 */
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
      $ pahole -JV dwarf_test.be
      File dwarf_test.be:
      [1] STRUCT S kind_flag=1 size=4 vlen=4
              j type_id=2 bitfield_size=5 bits_offset=0
              k type_id=2 bitfield_size=6 bits_offset=5
              m type_id=2 bitfield_size=5 bits_offset=11
              n type_id=2 bitfield_size=8 bits_offset=16
      [2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
    
      $ pahole -F btf dwarf_test.be
      struct S {
              int                        j:5;                  /*     0: 0  4 */
              int                        k:6;                  /*     0: 5  4 */
              int                        m:5;                  /*     0:11  4 */
              int                        n:8;                  /*     0:16  4 */
    
              /* size: 4, cachelines: 1, members: 4 */
              /* bit_padding: 8 bits */
              /* last cacheline: 4 bytes */
      };
    
    FOR REFERENCE. Relevant parts of DWARF output from GCC (clang outputs exactly
    the same data) for both little- and big-endian binaries:
    
      $ readelf -wi dwarf_test
      Contents of the .debug_info section:
      <snip>
       <1><2d>: Abbrev Number: 2 (DW_TAG_structure_type)
          <2e>   DW_AT_name        : S
          <30>   DW_AT_byte_size   : 4
          <31>   DW_AT_decl_file   : 1
          <32>   DW_AT_decl_line   : 1
          <33>   DW_AT_decl_column : 8
          <34>   DW_AT_sibling     : <0x71>
       <2><38>: Abbrev Number: 3 (DW_TAG_member)
          <39>   DW_AT_name        : j
          <3b>   DW_AT_decl_file   : 1
          <3c>   DW_AT_decl_line   : 2
          <3d>   DW_AT_decl_column : 6
          <3e>   DW_AT_type        : <0x71>
          <42>   DW_AT_byte_size   : 4
          <43>   DW_AT_bit_size    : 5
          <44>   DW_AT_bit_offset  : 27
          <45>   DW_AT_data_member_location: 0
       <2><46>: Abbrev Number: 3 (DW_TAG_member)
          <47>   DW_AT_name        : k
          <49>   DW_AT_decl_file   : 1
          <4a>   DW_AT_decl_line   : 3
          <4b>   DW_AT_decl_column : 6
          <4c>   DW_AT_type        : <0x71>
          <50>   DW_AT_byte_size   : 4
          <51>   DW_AT_bit_size    : 6
          <52>   DW_AT_bit_offset  : 21
          <53>   DW_AT_data_member_location: 0
       <2><54>: Abbrev Number: 3 (DW_TAG_member)
          <55>   DW_AT_name        : m
          <57>   DW_AT_decl_file   : 1
          <58>   DW_AT_decl_line   : 4
          <59>   DW_AT_decl_column : 6
          <5a>   DW_AT_type        : <0x71>
          <5e>   DW_AT_byte_size   : 4
          <5f>   DW_AT_bit_size    : 5
          <60>   DW_AT_bit_offset  : 16
          <61>   DW_AT_data_member_location: 0
       <2><62>: Abbrev Number: 3 (DW_TAG_member)
          <63>   DW_AT_name        : n
          <65>   DW_AT_decl_file   : 1
          <66>   DW_AT_decl_line   : 5
          <67>   DW_AT_decl_column : 6
          <68>   DW_AT_type        : <0x71>
          <6c>   DW_AT_byte_size   : 4
          <6d>   DW_AT_bit_size    : 8
          <6e>   DW_AT_bit_offset  : 8
          <6f>   DW_AT_data_member_location: 0
       <2><70>: Abbrev Number: 0
       <1><71>: Abbrev Number: 4 (DW_TAG_base_type)
          <72>   DW_AT_byte_size   : 4
          <73>   DW_AT_encoding    : 5        (signed)
          <74>   DW_AT_name        : int
      <snip>
    
      $ readelf -wi dwarf_test.be
      Contents of the .debug_info section:
      <snip>
       <1><2d>: Abbrev Number: 2 (DW_TAG_structure_type)
          <2e>   DW_AT_name        : S
          <30>   DW_AT_byte_size   : 4
          <31>   DW_AT_decl_file   : 1
          <32>   DW_AT_decl_line   : 1
          <33>   DW_AT_sibling     : <0x6c>
       <2><37>: Abbrev Number: 3 (DW_TAG_member)
          <38>   DW_AT_name        : j
          <3a>   DW_AT_decl_file   : 1
          <3b>   DW_AT_decl_line   : 2
          <3c>   DW_AT_type        : <0x6c>
          <40>   DW_AT_byte_size   : 4
          <41>   DW_AT_bit_size    : 5
          <42>   DW_AT_bit_offset  : 0
          <43>   DW_AT_data_member_location: 0
       <2><44>: Abbrev Number: 3 (DW_TAG_member)
          <45>   DW_AT_name        : k
          <47>   DW_AT_decl_file   : 1
          <48>   DW_AT_decl_line   : 3
          <49>   DW_AT_type        : <0x6c>
          <4d>   DW_AT_byte_size   : 4
          <4e>   DW_AT_bit_size    : 6
          <4f>   DW_AT_bit_offset  : 5
          <50>   DW_AT_data_member_location: 0
       <2><51>: Abbrev Number: 3 (DW_TAG_member)
          <52>   DW_AT_name        : m
          <54>   DW_AT_decl_file   : 1
          <55>   DW_AT_decl_line   : 4
          <56>   DW_AT_type        : <0x6c>
          <5a>   DW_AT_byte_size   : 4
          <5b>   DW_AT_bit_size    : 5
          <5c>   DW_AT_bit_offset  : 11
          <5d>   DW_AT_data_member_location: 0
       <2><5e>: Abbrev Number: 3 (DW_TAG_member)
          <5f>   DW_AT_name        : n
          <61>   DW_AT_decl_file   : 1
          <62>   DW_AT_decl_line   : 5
          <63>   DW_AT_type        : <0x6c>
          <67>   DW_AT_byte_size   : 4
          <68>   DW_AT_bit_size    : 8
          <69>   DW_AT_bit_offset  : 16
          <6a>   DW_AT_data_member_location: 0
      <snip>
    Signed-off-by: default avatarAndrii Nakryiko <andriin@fb.com>
    Cc: Alexei Starovoitov <ast@fb.com>
    Cc: Mark Wielaard <mark@klomp.org>
    Cc: Martin KaFai Lau <kafai@fb.com>
    Cc: Yonghong Song <yhs@fb.com>
    Cc: dwarves@vger.kernel.org
    Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
    c0fdc5e6