Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

libbpf: Fix BTF-to-C converter's padding logic

Turns out that btf_dump API doesn't handle a bunch of tricky corner
cases, as reported by Per, and further discovered using his testing
Python script ([0]).

This patch revamps btf_dump's padding logic significantly, making it
more correct and also avoiding unnecessary explicit padding, where
compiler would pad naturally. This overall topic turned out to be very
tricky and subtle, there are lots of subtle corner cases. The comments
in the code tries to give some clues, but comments themselves are
supposed to be paired with good understanding of C alignment and padding
rules. Plus some experimentation to figure out subtle things like
whether `long :0;` means that struct is now forced to be long-aligned
(no, it's not, turns out).

Anyways, Per's script, while not completely correct in some known
situations, doesn't show any obvious cases where this logic breaks, so
this is a nice improvement over the previous state of this logic.

Some selftests had to be adjusted to accommodate better use of natural
alignment rules, eliminating some unnecessary padding, or changing it to
`type: 0;` alignment markers.

Note also that for when we are in between bitfields, we emit explicit
bit size, while otherwise we use `: 0`, this feels much more natural in
practice.

Next patch will add few more test cases, found through randomized Per's
script.

[0] https://lore.kernel.org/bpf/85f83c333f5355c8ac026f835b18d15060725fcb.camel@ericsson.com/

Reported-by: Per Sundström XP <per.xp.sundstrom@ericsson.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20221212211505.558851-6-andrii@kernel.org

authored by

Andrii Nakryiko and committed by
Daniel Borkmann
ea2ce1ba 25a4481b

+163 -64
+122 -45
tools/lib/bpf/btf_dump.c
··· 830 830 } 831 831 } 832 832 833 + static int btf_natural_align_of(const struct btf *btf, __u32 id) 834 + { 835 + const struct btf_type *t = btf__type_by_id(btf, id); 836 + int i, align, vlen; 837 + const struct btf_member *m; 838 + 839 + if (!btf_is_composite(t)) 840 + return btf__align_of(btf, id); 841 + 842 + align = 1; 843 + m = btf_members(t); 844 + vlen = btf_vlen(t); 845 + for (i = 0; i < vlen; i++, m++) { 846 + align = max(align, btf__align_of(btf, m->type)); 847 + } 848 + 849 + return align; 850 + } 851 + 833 852 static bool btf_is_struct_packed(const struct btf *btf, __u32 id, 834 853 const struct btf_type *t) 835 854 { ··· 856 837 int align, i, bit_sz; 857 838 __u16 vlen; 858 839 859 - align = btf__align_of(btf, id); 860 - /* size of a non-packed struct has to be a multiple of its alignment*/ 861 - if (align && t->size % align) 840 + align = btf_natural_align_of(btf, id); 841 + /* size of a non-packed struct has to be a multiple of its alignment */ 842 + if (align && (t->size % align) != 0) 862 843 return true; 863 844 864 845 m = btf_members(t); 865 846 vlen = btf_vlen(t); 866 847 /* all non-bitfield fields have to be naturally aligned */ 867 848 for (i = 0; i < vlen; i++, m++) { 868 - align = btf__align_of(btf, m->type); 849 + align = btf_natural_align_of(btf, m->type); 869 850 bit_sz = btf_member_bitfield_size(t, i); 870 851 if (align && bit_sz == 0 && m->offset % (8 * align) != 0) 871 852 return true; ··· 878 859 return false; 879 860 } 880 861 881 - static int chip_away_bits(int total, int at_most) 882 - { 883 - return total % at_most ? : at_most; 884 - } 885 - 886 862 static void btf_dump_emit_bit_padding(const struct btf_dump *d, 887 - int cur_off, int m_off, int m_bit_sz, 888 - int align, int lvl) 863 + int cur_off, int next_off, int next_align, 864 + bool in_bitfield, int lvl) 889 865 { 890 - int off_diff = m_off - cur_off; 891 - int ptr_bits = d->ptr_sz * 8; 866 + const struct { 867 + const char *name; 868 + int bits; 869 + } pads[] = { 870 + {"long", d->ptr_sz * 8}, {"int", 32}, {"short", 16}, {"char", 8} 871 + }; 872 + int new_off, pad_bits, bits, i; 873 + const char *pad_type; 892 874 893 - if (off_diff <= 0) 894 - /* no gap */ 895 - return; 896 - if (m_bit_sz == 0 && off_diff < align * 8) 897 - /* natural padding will take care of a gap */ 898 - return; 875 + if (cur_off >= next_off) 876 + return; /* no gap */ 899 877 900 - while (off_diff > 0) { 901 - const char *pad_type; 902 - int pad_bits; 878 + /* For filling out padding we want to take advantage of 879 + * natural alignment rules to minimize unnecessary explicit 880 + * padding. First, we find the largest type (among long, int, 881 + * short, or char) that can be used to force naturally aligned 882 + * boundary. Once determined, we'll use such type to fill in 883 + * the remaining padding gap. In some cases we can rely on 884 + * compiler filling some gaps, but sometimes we need to force 885 + * alignment to close natural alignment with markers like 886 + * `long: 0` (this is always the case for bitfields). Note 887 + * that even if struct itself has, let's say 4-byte alignment 888 + * (i.e., it only uses up to int-aligned types), using `long: 889 + * X;` explicit padding doesn't actually change struct's 890 + * overall alignment requirements, but compiler does take into 891 + * account that type's (long, in this example) natural 892 + * alignment requirements when adding implicit padding. We use 893 + * this fact heavily and don't worry about ruining correct 894 + * struct alignment requirement. 895 + */ 896 + for (i = 0; i < ARRAY_SIZE(pads); i++) { 897 + pad_bits = pads[i].bits; 898 + pad_type = pads[i].name; 903 899 904 - if (ptr_bits > 32 && off_diff > 32) { 905 - pad_type = "long"; 906 - pad_bits = chip_away_bits(off_diff, ptr_bits); 907 - } else if (off_diff > 16) { 908 - pad_type = "int"; 909 - pad_bits = chip_away_bits(off_diff, 32); 910 - } else if (off_diff > 8) { 911 - pad_type = "short"; 912 - pad_bits = chip_away_bits(off_diff, 16); 913 - } else { 914 - pad_type = "char"; 915 - pad_bits = chip_away_bits(off_diff, 8); 900 + new_off = roundup(cur_off, pad_bits); 901 + if (new_off <= next_off) 902 + break; 903 + } 904 + 905 + if (new_off > cur_off && new_off <= next_off) { 906 + /* We need explicit `<type>: 0` aligning mark if next 907 + * field is right on alignment offset and its 908 + * alignment requirement is less strict than <type>'s 909 + * alignment (so compiler won't naturally align to the 910 + * offset we expect), or if subsequent `<type>: X`, 911 + * will actually completely fit in the remaining hole, 912 + * making compiler basically ignore `<type>: X` 913 + * completely. 914 + */ 915 + if (in_bitfield || 916 + (new_off == next_off && roundup(cur_off, next_align * 8) != new_off) || 917 + (new_off != next_off && next_off - new_off <= new_off - cur_off)) 918 + /* but for bitfields we'll emit explicit bit count */ 919 + btf_dump_printf(d, "\n%s%s: %d;", pfx(lvl), pad_type, 920 + in_bitfield ? new_off - cur_off : 0); 921 + cur_off = new_off; 922 + } 923 + 924 + /* Now we know we start at naturally aligned offset for a chosen 925 + * padding type (long, int, short, or char), and so the rest is just 926 + * a straightforward filling of remaining padding gap with full 927 + * `<type>: sizeof(<type>);` markers, except for the last one, which 928 + * might need smaller than sizeof(<type>) padding. 929 + */ 930 + while (cur_off != next_off) { 931 + bits = min(next_off - cur_off, pad_bits); 932 + if (bits == pad_bits) { 933 + btf_dump_printf(d, "\n%s%s: %d;", pfx(lvl), pad_type, pad_bits); 934 + cur_off += bits; 935 + continue; 916 936 } 917 - btf_dump_printf(d, "\n%s%s: %d;", pfx(lvl), pad_type, pad_bits); 918 - off_diff -= pad_bits; 937 + /* For the remainder padding that doesn't cover entire 938 + * pad_type bit length, we pick the smallest necessary type. 939 + * This is pure aesthetics, we could have just used `long`, 940 + * but having smallest necessary one communicates better the 941 + * scale of the padding gap. 942 + */ 943 + for (i = ARRAY_SIZE(pads) - 1; i >= 0; i--) { 944 + pad_type = pads[i].name; 945 + pad_bits = pads[i].bits; 946 + if (pad_bits < bits) 947 + continue; 948 + 949 + btf_dump_printf(d, "\n%s%s: %d;", pfx(lvl), pad_type, bits); 950 + cur_off += bits; 951 + break; 952 + } 919 953 } 920 954 } 921 955 ··· 988 916 { 989 917 const struct btf_member *m = btf_members(t); 990 918 bool is_struct = btf_is_struct(t); 991 - int align, i, packed, off = 0; 919 + bool packed, prev_bitfield = false; 920 + int align, i, off = 0; 992 921 __u16 vlen = btf_vlen(t); 993 922 923 + align = btf__align_of(d->btf, id); 994 924 packed = is_struct ? btf_is_struct_packed(d->btf, id, t) : 0; 995 925 996 926 btf_dump_printf(d, "%s%s%s {", ··· 1002 928 1003 929 for (i = 0; i < vlen; i++, m++) { 1004 930 const char *fname; 1005 - int m_off, m_sz; 931 + int m_off, m_sz, m_align; 932 + bool in_bitfield; 1006 933 1007 934 fname = btf_name_of(d, m->name_off); 1008 935 m_sz = btf_member_bitfield_size(t, i); 1009 936 m_off = btf_member_bit_offset(t, i); 1010 - align = packed ? 1 : btf__align_of(d->btf, m->type); 937 + m_align = packed ? 1 : btf__align_of(d->btf, m->type); 1011 938 1012 - btf_dump_emit_bit_padding(d, off, m_off, m_sz, align, lvl + 1); 939 + in_bitfield = prev_bitfield && m_sz != 0; 940 + 941 + btf_dump_emit_bit_padding(d, off, m_off, m_align, in_bitfield, lvl + 1); 1013 942 btf_dump_printf(d, "\n%s", pfx(lvl + 1)); 1014 943 btf_dump_emit_type_decl(d, m->type, fname, lvl + 1); 1015 944 1016 945 if (m_sz) { 1017 946 btf_dump_printf(d, ": %d", m_sz); 1018 947 off = m_off + m_sz; 948 + prev_bitfield = true; 1019 949 } else { 1020 950 m_sz = max((__s64)0, btf__resolve_size(d->btf, m->type)); 1021 951 off = m_off + m_sz * 8; 952 + prev_bitfield = false; 1022 953 } 954 + 1023 955 btf_dump_printf(d, ";"); 1024 956 } 1025 957 1026 958 /* pad at the end, if necessary */ 1027 - if (is_struct) { 1028 - align = packed ? 1 : btf__align_of(d->btf, id); 1029 - btf_dump_emit_bit_padding(d, off, t->size * 8, 0, align, 1030 - lvl + 1); 1031 - } 959 + if (is_struct) 960 + btf_dump_emit_bit_padding(d, off, t->size * 8, align, false, lvl + 1); 1032 961 1033 962 /* 1034 963 * Keep `struct empty {}` on a single line,
+1 -1
tools/testing/selftests/bpf/progs/btf_dump_test_case_bitfields.c
··· 53 53 */ 54 54 /* ------ END-EXPECTED-OUTPUT ------ */ 55 55 struct bitfield_mixed_with_others { 56 - long: 4; /* char is enough as a backing field */ 56 + char: 4; /* char is enough as a backing field */ 57 57 int a: 4; 58 58 /* 8-bit implicit padding */ 59 59 short b; /* combined with previous bitfield */
+40 -18
tools/testing/selftests/bpf/progs/btf_dump_test_case_padding.c
··· 19 19 /* 20 20 *struct padded_explicitly { 21 21 * int a; 22 - * int: 32; 22 + * long: 0; 23 23 * int b; 24 24 *}; 25 25 * ··· 28 28 29 29 struct padded_explicitly { 30 30 int a; 31 - int: 1; /* algo will explicitly pad with full 32 bits here */ 31 + int: 1; /* algo will emit aligning `long: 0;` here */ 32 32 int b; 33 33 }; 34 34 35 35 /* ----- START-EXPECTED-OUTPUT ----- */ 36 - /* 37 - *struct padded_a_lot { 38 - * int a; 39 - * long: 32; 40 - * long: 64; 41 - * long: 64; 42 - * int b; 43 - *}; 44 - * 45 - */ 46 - /* ------ END-EXPECTED-OUTPUT ------ */ 47 - 48 36 struct padded_a_lot { 49 37 int a; 50 - /* 32 bit of implicit padding here, which algo will make explicit */ 51 38 long: 64; 52 39 long: 64; 53 40 int b; 54 41 }; 42 + 43 + /* ------ END-EXPECTED-OUTPUT ------ */ 55 44 56 45 /* ----- START-EXPECTED-OUTPUT ----- */ 57 46 /* 58 47 *struct padded_cache_line { 59 48 * int a; 60 - * long: 32; 61 49 * long: 64; 62 50 * long: 64; 63 51 * long: 64; 64 52 * int b; 65 - * long: 32; 66 53 * long: 64; 67 54 * long: 64; 68 55 * long: 64; ··· 72 85 *struct zone { 73 86 * int a; 74 87 * short b; 75 - * short: 16; 88 + * long: 0; 76 89 * struct zone_padding __pad__; 77 90 *}; 78 91 * ··· 95 108 long: 64; 96 109 }; 97 110 111 + struct padding_weird_1 { 112 + int a; 113 + long: 64; 114 + short: 16; 115 + short b; 116 + }; 117 + 118 + /* ------ END-EXPECTED-OUTPUT ------ */ 119 + 120 + /* ----- START-EXPECTED-OUTPUT ----- */ 121 + /* 122 + *struct padding_weird_2 { 123 + * long: 56; 124 + * char a; 125 + * long: 56; 126 + * char b; 127 + * char: 8; 128 + *}; 129 + * 130 + */ 131 + /* ------ END-EXPECTED-OUTPUT ------ */ 132 + struct padding_weird_2 { 133 + int: 32; /* these paddings will be collapsed into `long: 56;` */ 134 + short: 16; 135 + char: 8; 136 + char a; 137 + int: 32; /* these paddings will be collapsed into `long: 56;` */ 138 + short: 16; 139 + char: 8; 140 + char b; 141 + char: 8; 142 + }; 143 + 98 144 /* ------ END-EXPECTED-OUTPUT ------ */ 99 145 100 146 int f(struct { ··· 137 117 struct padded_cache_line _4; 138 118 struct zone _5; 139 119 struct padding_wo_named_members _6; 120 + struct padding_weird_1 _7; 121 + struct padding_weird_2 _8; 140 122 } *_) 141 123 { 142 124 return 0;