[PATCH v2] bpf: add line_info support to BTF.ext section
Cupertino Miranda
cupertino.miranda@oracle.com
Mon Mar 2 11:58:55 GMT 2026
Hi David, Jose,
Indeed it is much better like this.
Patch as been pushed with requested change.
Thanks,
Cupertino
On 27-02-2026 5:46 PM, Jose E. Marchesi wrote:
>
>> Hi Cupertino,
>>
>> On 2/27/26 08:08, Cupertino Miranda wrote:
>>> Hi everyone, David
>>>
>>> This is v2 for line_info support.
>>> Looking forward to your review.
>>
>> Very nice.
>> I tried it with a couple of tests locally and it works well, now
>> we get accurate source line info from verifier errors :)
>>
>> I have one small comment below, but otherwise LGTM.
>> OK with that addressed.
>>
>> Thanks!
>>
>>>
>>> Cheers,
>>> Cupertino
>>>
>>> Changes from v1:
>>> - Corrected typos.
>>> - Fixed line_col encoding and created macros.
>>> - Added a test case for line_info.
>>> - Improved line and column info repetition detection.
>>> Previous patch was generating multiple entries for the same line and
>>> column data for consecutive labels.
>>>
>>>
>>> This patch adds line_info debug information support to .BTF.ext
>>> sections.
>>>
>>> Line info information is used by the BPF verifier to improve error
>>> reporting and give more precise source code referenced errors.
>>>
>>> gcc/ChangeLog:
>>> PR target/113453
>>> * config/bpf/bpf-protos.h (bpf_output_call): Change prototype.
>>> * config/bpf/bpf.cc (bpf_output_call): Change to adapt operands
>>> and return
>>> the instruction template instead of immediately emit asm and
>>> not allow proper final expected execution flow.
>>> (bpf_output_line_info): Add function to introduce line info
>>> entries in respective structures
>>> (bpf_asm_out_unwind_emit): Add function as hook to
>>> TARGET_ASM_UNWIND_EMIT. This hook is called before any
>>> instruction is emitted.
>>> * config/bpf/bpf.md: Change calls to bpf_output_call.
>>> * config/bpf/btfext-out.cc (struct btf_ext_lineinfo): Add fields
>>> to struct.
>>> (bpf_create_lineinfo, btf_add_line_info_for): Add support
>>> function to insert line_info data in respective structures.
>>> (output_btfext_line_info): Function to emit line_info data in
>>> .BTF.ext section.
>>> (btf_ext_output): Call output_btfext_line_info.
>>> * config/bpf/btfext-out.h: Add prototype for
>>> btf_add_line_info_for.
>>>
>>> gcc/testsuite/ChangeLog:
>>> PR target/113453
>>> * gcc.target/bpf/btfext-funcinfo.c: Adapt test.
>>> * gcc.target/bpf/btfext-lineinfo.c: New test.
>>> ---
>>> gcc/config/bpf/bpf-protos.h | 2 +-
>>> gcc/config/bpf/bpf.cc | 105 +++++++++++++---
>>> gcc/config/bpf/bpf.md | 4 +-
>>> gcc/config/bpf/btfext-out.cc | 114 +++++++++++++++++-
>>> gcc/config/bpf/btfext-out.h | 4 +
>>> .../gcc.target/bpf/btfext-funcinfo.c | 3 +-
>>> .../gcc.target/bpf/btfext-lineinfo.c | 51 ++++++++
>>> 7 files changed, 262 insertions(+), 21 deletions(-)
>>> create mode 100644 gcc/testsuite/gcc.target/bpf/btfext-lineinfo.c
>>>
>>> diff --git a/gcc/config/bpf/bpf-protos.h b/gcc/config/bpf/bpf-protos.h
>>> index 4a4799e9bf22..1113cc3e6d17 100644
>>> --- a/gcc/config/bpf/bpf-protos.h
>>> +++ b/gcc/config/bpf/bpf-protos.h
>>> @@ -23,7 +23,7 @@ along with GCC; see the file COPYING3. If not see
>>> /* Routines implemented in bpf.cc. */
>>>
>>> extern HOST_WIDE_INT bpf_initial_elimination_offset (int, int);
>>> -extern const char *bpf_output_call (rtx);
>>> +extern const char *bpf_output_call (const char *templ, rtx *, int target_index);
>>> extern void bpf_target_macros (cpp_reader *);
>>> extern void bpf_print_operand (FILE *, rtx, int);
>>> extern void bpf_print_operand_address (FILE *, rtx);
>>> diff --git a/gcc/config/bpf/bpf.cc b/gcc/config/bpf/bpf.cc
>>> index d91013ad140b..4773d789d8e3 100644
>>> --- a/gcc/config/bpf/bpf.cc
>>> +++ b/gcc/config/bpf/bpf.cc
>>> @@ -745,14 +745,12 @@ bpf_output_destructor (rtx symbol, int priority ATTRIBUTE_UNUSED)
>>> bpf.md. */
>>>
>>> const char *
>>> -bpf_output_call (rtx target)
>>> +bpf_output_call (const char *templ, rtx *operands, int target_index)
>>> {
>>> - rtx xops[1];
>>> -
>>> + rtx target = operands[target_index];
>>> switch (GET_CODE (target))
>>> {
>>> case CONST_INT:
>>> - output_asm_insn ("call\t%0", &target);
>>> break;
>>> case SYMBOL_REF:
>>> {
>>> @@ -765,26 +763,21 @@ bpf_output_call (rtx target)
>>> {
>>> tree attr_args = TREE_VALUE (attr);
>>>
>>> - xops[0] = GEN_INT (TREE_INT_CST_LOW (TREE_VALUE (attr_args)));
>>> - output_asm_insn ("call\t%0", xops);
>>> + operands[target_index] =
>>> + GEN_INT (TREE_INT_CST_LOW (TREE_VALUE (attr_args)));
>>> }
>>> - else
>>> - output_asm_insn ("call\t%0", &target);
>>> -
>>> break;
>>> }
>>> default:
>>> - if (TARGET_XBPF)
>>> - output_asm_insn ("call\t%0", &target);
>>> - else
>>> + if (!TARGET_XBPF)
>>> {
>>> error ("indirect call in function, which are not supported by eBPF");
>>> - output_asm_insn ("call 0", NULL);
>>> + operands[target_index] = GEN_INT (0);
>>> }
>>> break;
>>> }
>>>
>>> - return "";
>>> + return templ;
>>> }
>>>
>>> const char *
>>> @@ -1145,6 +1138,90 @@ bpf_debug_unwind_info ()
>>> #undef TARGET_DEBUG_UNWIND_INFO
>>> #define TARGET_DEBUG_UNWIND_INFO bpf_debug_unwind_info
>>>
>>> +/* Create a BTF.ext line_info entry. */
>>> +
>>> +static void
>>> +bpf_output_line_info (FILE *asm_out_file, rtx_insn *insn)
>>> +{
>>> + static unsigned int line_info_label = 1;
>>> + static tree cfun_decl = NULL_TREE;
>>> + static bool func_start_added = false;
>>> + const char *label = NULL;
>>> + unsigned int loc = 0;
>>> + expanded_location exploc = {NULL, 0, 0, NULL, false};
>>> + static expanded_location prev_exploc = {NULL, 0, 0, NULL, false};
>>> +
>>> + if(!btf_debuginfo_p () || flag_building_libgcc)
>>> + return;
>>> +
>>> + gcc_assert (insn != NULL_RTX);
>>> +
>>> + if (current_function_decl != cfun_decl
>>> + && GET_CODE (insn) == NOTE)
>>> + {
>>> + label = current_function_func_begin_label;
>>> + loc = DECL_SOURCE_LOCATION (current_function_decl);
>>> + exploc = expand_location (loc);
>>> + func_start_added = true;
>>> + prev_exploc = exploc;
>>> + }
>>> + else
>>> + {
>>> + if (GET_CODE (insn) == NOTE)
>>> + return;
>>> +
>>> + /* Already added a label for this location. This might not be fully
>>> + accurate but it is better then adding 2 entries on the same location,
>>> + which is incompatible with the verifier expectations. */
>>> + if (func_start_added == true)
>>> + {
>>> + func_start_added = false;
>>> + return;
>>> + }
>>> +
>>> +
>>> + if (!INSN_HAS_LOCATION (insn))
>>> + return;
>>> +
>>> + exploc = insn_location (insn);
>>> +
>>> + if (exploc.file == NULL || exploc.line == 0
>>> + || (exploc.line == prev_exploc.line
>>> + && exploc.column == prev_exploc.column))
>>> + return;
>>> +
>>> + prev_exploc = exploc;
>>> +
>>> + char tmp_label[25];
>>> + sprintf (tmp_label, "LI%u", line_info_label);
>>> + ASM_OUTPUT_LABEL (asm_out_file, tmp_label);
>>> + line_info_label += 1;
>>> + label = const_cast<char *> (ggc_strdup (tmp_label));
>>> + }
>>> +
>>> + cfun_decl = current_function_decl;
>>> +
>>> + if (exploc.file != NULL && exploc.line != 0)
>>> + btf_add_line_info_for (label, exploc.file, exploc.line, exploc.column);
>>> +}
>>> +
>>> +
>>> +/* This hook is defined as a way for BPF target to create a label before each
>>> + emitted instruction and emit line_info information. This data is later
>>> + output in .BTF.ext section.
>>> + This approach expects TARGET_EMIT_BEFORE_INSN to be returning TRUE as
>>> + this function needs to be called before the instruction is emitted. Current
>>> + default behaviour returns TRUE and the hook is left undefined. */
>>> +
>>> +static void
>>> +bpf_asm_out_unwind_emit (FILE *asm_out_file, rtx_insn *insn)
>>> +{
>>> + bpf_output_line_info (asm_out_file, insn);
>>> +}
>>> +
>>> +#undef TARGET_ASM_UNWIND_EMIT
>>> +#define TARGET_ASM_UNWIND_EMIT bpf_asm_out_unwind_emit
>>> +
>>> /* Output assembly directives to assemble data of various sized and
>>> alignments. */
>>>
>>> diff --git a/gcc/config/bpf/bpf.md b/gcc/config/bpf/bpf.md
>>> index 0eec006d3cfe..bc739f2746c0 100644
>>> --- a/gcc/config/bpf/bpf.md
>>> +++ b/gcc/config/bpf/bpf.md
>>> @@ -545,7 +545,7 @@
>>> ;; operands[2] is next_arg_register
>>> ;; operands[3] is struct_value_size_rtx.
>>> ""
>>> - { return bpf_output_call (operands[0]); }
>>> + { return bpf_output_call ("call\t%0", operands, 0); }
>>> [(set_attr "type" "jmp")])
>>>
>>> (define_expand "call_value"
>>> @@ -568,7 +568,7 @@
>>> ;; operands[3] is next_arg_register
>>> ;; operands[4] is struct_value_size_rtx.
>>> ""
>>> - { return bpf_output_call (operands[1]); }
>>> + { return bpf_output_call ("call\t%1", operands, 1); }
>>> [(set_attr "type" "jmp")])
>>>
>>> (define_insn "sibcall"
>>> diff --git a/gcc/config/bpf/btfext-out.cc b/gcc/config/bpf/btfext-out.cc
>>> index 04bc60d69b0e..9a7e34aeb634 100644
>>> --- a/gcc/config/bpf/btfext-out.cc
>>> +++ b/gcc/config/bpf/btfext-out.cc
>>> @@ -32,6 +32,7 @@
>>> #include "rtl.h"
>>> #include "tree-pretty-print.h"
>>> #include "cgraph.h"
>>> +#include "toplev.h" /* get_src_pwd */
>>>
>>> #include "btfext-out.h"
>>>
>>> @@ -124,14 +125,19 @@ struct GTY ((chain_next ("%h.next"))) btf_ext_funcinfo
>>> /* A lineinfo record, in the .BTF.ext lineinfo section. */
>>> struct GTY ((chain_next ("%h.next"))) btf_ext_lineinfo
>>> {
>>> - uint32_t insn_off; /* Offset of the instruction. */
>>> + const char *insn_label; /* Instruction label. */
>>> + const char *file_name; /* Source-code file name. */
>>> uint32_t file_name_off; /* Offset of file name in BTF string table. */
>>> uint32_t line_off; /* Offset of source line in BTF string table. */
>>> - uint32_t line_col; /* Line number (bits 31-11) and column (11-0). */
>>> + uint32_t line_col; /* Line number (bits 31-10) and column (9-0). */
>>>
>>> struct btf_ext_lineinfo *next; /* Linked list to collect line_info elems. */
>>> };
>>>
>>> +#define BTF_LINE_INFO_LINE(line_col) ((line_col) >> 10)
>>> +#define BTF_LINE_INFO_COLUMN(line_col) ((line_col) & 0x3ff)
>>> +#define BTF_LINE_INFO_LINE_COL(line, column) (line << 10 | (column & 0x3ff))
>>> +
>>> /* Internal representation of a BPF CO-RE relocation record. */
>>> struct GTY ((chain_next ("%h.next"))) btf_ext_core_reloc {
>>> ctf_dtdef_ref bpfcr_type; /* BTF type involved in relocation. */
>>> @@ -235,6 +241,26 @@ bpf_create_or_find_funcinfo (const char *fnname, const char *sec_name,
>>> return *head;
>>> }
>>>
>>> +/* Function to create a lineinfo node in info. */
>>> +
>>> +static struct btf_ext_lineinfo *
>>> +bpf_create_lineinfo (const char *sec_name, btf_ext_info_sec **in_sec = NULL)
>>> +{
>>> + struct btf_ext_info_sec *sec_elem =
>>> + btfext_info_sec_find_or_add (sec_name, true);
>>> +
>>> + if (in_sec != NULL)
>>> + *in_sec = sec_elem;
>>> +
>>> + struct btf_ext_lineinfo **head =
>>> + SEARCH_NODE_AND_RETURN(struct btf_ext_lineinfo,
>>> + sec_elem->line_info.head,
>>> + false);
>>> + *head = ggc_cleared_alloc<struct btf_ext_lineinfo> ();
>>> +
>>> + return *head;
>>> +}
>>> +
>>> /* Function to create a core_reloc node in info. */
>>>
>>> static struct btf_ext_core_reloc *
>>> @@ -438,6 +464,47 @@ btf_validate_funcinfo (btf_ext_info_sec *sec)
>>> }
>>> }
>>>
>>> +#define STR_BUFF_SIZE 256
>>> +
>>> +struct btf_ext_lineinfo *
>>> +btf_add_line_info_for (const char *label, const char *filename,
>>> + unsigned int line, unsigned int column)
>>> +{
>>> + const char *sec_name = decl_section_name (current_function_decl);
>>> +
>>> + if (sec_name == NULL)
>>> + sec_name = ".text";
>>> +
>>> + struct btf_ext_info_sec *sec = NULL;
>>> + struct btf_ext_lineinfo *info =
>>> + bpf_create_lineinfo (sec_name, &sec);
>>> +
>>> + info->insn_label = label;
>>> +
>>> + if (!IS_DIR_SEPARATOR (filename[0]))
>>> + {
>>> + char full_filename[STR_BUFF_SIZE];
>>> +
>>> + /* Filename is a relative path. */
>>> + const char * cu_pwd = get_src_pwd ();
>>> + gcc_assert (strlen (cu_pwd) + strlen (filename) + 2 < STR_BUFF_SIZE);
>>> +
>>> + sprintf (full_filename, "%s%c%s", cu_pwd, DIR_SEPARATOR, filename);
>>
>> On second look I don't like using gcc_assert here; this will result in an
>> ICE simply if the input filename is very long...
>>
>> While I doubt that will occur much in practice, we should still deal with
>> it gracefully. IMO just truncating via e.g. snprintf would be fine and
>> much better than causing an ICE.
>
> There is no need to truncate.
> Just use xasprintf.
>
>>
>>> + info->file_name = ggc_strdup (full_filename);
>>> + }
>>> + else
>>> + /* Filename is an absolute path. */
>>> + info->file_name = ggc_strdup (filename);
>>> +
>>> + info->file_name_off = btf_ext_add_string (info->file_name);
>>> + info->line_off = 0;
>>> + info->line_col = BTF_LINE_INFO_LINE_COL (line, column);
>>> +
>>> + sec->line_info.num_info += 1;
>>> + return info;
>>> +}
>>> +#undef STR_BUFF_SIZE
>>> +
>>> /* Compute the section size in section for func_info, line_info and core_info
>>> regions of .BTF.ext. */
>>>
>>> @@ -545,6 +612,48 @@ output_btfext_func_info (struct btf_ext_info_sec *sec)
>>> }
>>> }
>>>
>>> +/* Outputs line_info region on .BTF.ext. */
>>> +
>>> +static void
>>> +output_btfext_line_info (struct btf_ext_info_sec *sec)
>>> +{
>>> + unsigned int str_aux_off = ctfc_get_strtab_len (ctf_get_tu_ctfc (),
>>> + CTF_STRTAB);
>>> + bool executed = false;
>>> + while (sec != NULL)
>>> + {
>>> + uint32_t count = 0;
>>> + if (sec->line_info.num_info > 0)
>>> + {
>>> + if (executed == false && (executed = true))
>>> + dw2_asm_output_data (4, 16, "LineInfo entry size");
>>> + dw2_asm_output_data (4, sec->sec_name_off + str_aux_off,
>>> + "LineInfo section string for %s",
>>> + sec->sec_name);
>>> + dw2_asm_output_data (4, sec->line_info.num_info, "Number of entries");
>>> +
>>> + struct btf_ext_lineinfo *elem = sec->line_info.head;
>>> + while (elem != NULL)
>>> + {
>>> + count += 1;
>>> + dw2_asm_output_offset (4, elem->insn_label, NULL, "insn_label");
>>> +
>>> + unsigned int file_name_off = btf_ext_add_string (elem->file_name);
>>> + dw2_asm_output_data (4, file_name_off + str_aux_off,
>>> + "file_name_off");
>>> + dw2_asm_output_data (4, elem->line_off, "line_off");
>>> + dw2_asm_output_data (4, elem->line_col, "(line, col) (%u, %u)",
>>> + BTF_LINE_INFO_LINE (elem->line_col),
>>> + BTF_LINE_INFO_COLUMN (elem->line_col));
>>> + elem = elem->next;
>>> + }
>>> + }
>>> +
>>> + gcc_assert (count == sec->line_info.num_info);
>>> + sec = sec->next;
>>> + }
>>> +}
>>> +
>>> /* Output all CO-RE relocation sections. */
>>>
>>> static void
>>> @@ -622,6 +731,7 @@ btf_ext_output (void)
>>>
>>> output_btfext_header ();
>>> output_btfext_func_info (btf_ext);
>>> + output_btfext_line_info (btf_ext);
>>> if (TARGET_BPF_CORE)
>>> output_btfext_core_sections ();
>>>
>>> diff --git a/gcc/config/bpf/btfext-out.h b/gcc/config/bpf/btfext-out.h
>>> index a4c87ffd1f02..fe9b3768643e 100644
>>> --- a/gcc/config/bpf/btfext-out.h
>>> +++ b/gcc/config/bpf/btfext-out.h
>>> @@ -99,6 +99,10 @@ extern int bpf_core_get_sou_member_index (ctf_container_ref, const tree);
>>>
>>> struct btf_ext_funcinfo *btf_add_func_info_for (tree decl,
>>> const char *label);
>>> +struct btf_ext_lineinfo *
>>> +btf_add_line_info_for (const char *label, const char *filename,
>>> + unsigned int line, unsigned int column);
>>> +
>>> unsigned int btf_ext_add_string (const char *str);
>>>
>>> #ifdef __cplusplus
>>> diff --git a/gcc/testsuite/gcc.target/bpf/btfext-funcinfo.c b/gcc/testsuite/gcc.target/bpf/btfext-funcinfo.c
>>> index fbbefeae68f0..f7f5b44b1ff4 100644
>>> --- a/gcc/testsuite/gcc.target/bpf/btfext-funcinfo.c
>>> +++ b/gcc/testsuite/gcc.target/bpf/btfext-funcinfo.c
>>> @@ -39,6 +39,5 @@ int bar_func (struct T *t)
>>> /* { dg-final { scan-assembler-times "ascii \"bar_sec.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
>>> /* { dg-final { scan-assembler-times "FuncInfo entry size" 1 } } */
>>>
>>> -/* { dg-final { scan-assembler-times ".4byte\t0x1\t# Number of entries" 3 } } */
>>> -/* { dg-final { scan-assembler-times ".4byte\t0x2\t# Number of entries" 1 } } */
>>> +/* { dg-final { scan-assembler-times "FuncInfo section\[^\n\]*\n\[^0\]*0x1\t# Number of entries" 2 } } */
>>> /* { dg-final { scan-assembler-times "Required padding" 1 } } */
>>> diff --git a/gcc/testsuite/gcc.target/bpf/btfext-lineinfo.c b/gcc/testsuite/gcc.target/bpf/btfext-lineinfo.c
>>> new file mode 100644
>>> index 000000000000..dc1c297d663e
>>> --- /dev/null
>>> +++ b/gcc/testsuite/gcc.target/bpf/btfext-lineinfo.c
>>> @@ -0,0 +1,51 @@
>>> +/* { dg-do compile } */
>>> +/* { dg-options "-O2 -dA -gbtf -masm=normal" } */
>>> +
>>> +struct T {
>>> + int a;
>>> + int b;
>>> + struct U {
>>> + int c;
>>> + struct V {
>>> + int d;
>>> + int e[4];
>>> + int f;
>>> + } v;
>>> + } u;
>>> +} __attribute__((preserve_access_index));
>>> +
>>> +__attribute__((section("foo_sec"), used))
>>> +int foo_func (struct T *t)
>>> +{
>>> + t->u.c = 5;
>>> + return t->u.v.e[3];
>>> +}
>>> +
>>> +__attribute__((section("bar_sec"), used))
>>> +int bar_func (struct T *t)
>>> +{
>>> + int *x = &(t->u.v.f);
>>> + int old = *x;
>>> + *x = 4;
>>> + return old;
>>> +}
>>> +
>>> +/* { dg-final { scan-assembler-times "LineInfo section string for foo_sec" 1 } } */
>>> +/* { dg-final { scan-assembler-times "LineInfo section string for bar_sec" 1 } } */
>>> +/* { dg-final { scan-assembler-times "label for function foo_func" 1 } } */
>>> +/* { dg-final { scan-assembler-times "label for function bar_func" 1 } } */
>>> +
>>> +
>>> +/* { dg-final { scan-assembler-times "btfext-lineinfo\.c.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
>>> +/* { dg-final { scan-assembler-times "4byte\[\t \]+\.LFB.\[\t \]+# insn_label" 2 } } */
>>> +/* { dg-final { scan-assembler-times "4byte\[\t \]+LI.\[\t \]+# insn_label" 6 } } */
>>> +
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\)" 8 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(18, 5\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(20, 10\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(21, 18\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(22, 1\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(25, 5\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(28, 7\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(29, 6\\)" 1 } } */
>>> +/* { dg-final { scan-assembler-times "# \\(line, col\\) \\(31, 1\\)" 1 } } */
More information about the bpf
mailing list