blob: 45b1c1081164d1fe213148e7673dee53de5ad488 [file]
// SPDX-License-Identifier: GPL-2.0-only
/*
* gen_lineinfo.c - Generate address-to-source-line lookup tables from DWARF
*
* Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
*
* Reads DWARF .debug_line from a vmlinux ELF file and outputs an assembly
* file containing sorted lookup tables that the kernel uses to annotate
* stack traces with source file:line information.
*
* Requires libdw from elfutils.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <elfutils/libdw.h>
#include <dwarf.h>
#include <elf.h>
#include <gelf.h>
#include <limits.h>
#include "../include/linux/mod_lineinfo.h"
static int module_mode;
static unsigned int skipped_overflow;
/* .text range for module mode (keep only runtime code) */
static unsigned long long text_section_start;
static unsigned long long text_section_end;
struct line_entry {
unsigned int offset; /* offset from _text */
unsigned int file_id;
unsigned int line;
};
struct file_entry {
char *name;
unsigned int id;
unsigned int str_offset;
};
static struct line_entry *entries;
static unsigned int num_entries;
static unsigned int entries_capacity;
static struct file_entry *files;
static unsigned int num_files;
static unsigned int files_capacity;
#define FILE_HASH_BITS 13
#define FILE_HASH_SIZE (1 << FILE_HASH_BITS)
struct file_hash_entry {
const char *name;
unsigned int id;
};
static struct file_hash_entry file_hash[FILE_HASH_SIZE];
static unsigned int hash_str(const char *s)
{
unsigned int h = 5381;
for (; *s; s++)
h = h * 33 + (unsigned char)*s;
return h & (FILE_HASH_SIZE - 1);
}
static void add_entry(unsigned int offset, unsigned int file_id,
unsigned int line)
{
if (num_entries >= entries_capacity) {
entries_capacity = entries_capacity ? entries_capacity * 2 : 65536;
entries = realloc(entries, entries_capacity * sizeof(*entries));
if (!entries) {
fprintf(stderr, "out of memory\n");
exit(1);
}
}
entries[num_entries].offset = offset;
entries[num_entries].file_id = file_id;
entries[num_entries].line = line;
num_entries++;
}
static unsigned int find_or_add_file(const char *name)
{
unsigned int h = hash_str(name);
/* Open-addressing lookup with linear probing */
while (file_hash[h].name) {
if (!strcmp(file_hash[h].name, name))
return file_hash[h].id;
h = (h + 1) & (FILE_HASH_SIZE - 1);
}
if (num_files >= 65535) {
fprintf(stderr,
"gen_lineinfo: too many source files (%u > 65535)\n",
num_files);
exit(1);
}
if (num_files >= files_capacity) {
files_capacity = files_capacity ? files_capacity * 2 : 4096;
files = realloc(files, files_capacity * sizeof(*files));
if (!files) {
fprintf(stderr, "out of memory\n");
exit(1);
}
}
files[num_files].name = strdup(name);
files[num_files].id = num_files;
/* Insert into hash table (points to files[] entry) */
file_hash[h].name = files[num_files].name;
file_hash[h].id = num_files;
num_files++;
return num_files - 1;
}
/*
* Well-known top-level directories in the kernel source tree.
* Used as a fallback to recover relative paths from absolute DWARF paths
* when comp_dir doesn't match (e.g. O= out-of-tree builds where comp_dir
* is the build directory but source paths point into the source tree).
*/
static const char * const kernel_dirs[] = {
"arch/", "block/", "certs/", "crypto/", "drivers/", "fs/",
"include/", "init/", "io_uring/", "ipc/", "kernel/", "lib/",
"mm/", "net/", "rust/", "samples/", "scripts/", "security/",
"sound/", "tools/", "usr/", "virt/",
};
/*
* Strip a filename to a kernel-relative path.
*
* For absolute paths, strip the comp_dir prefix (from DWARF) to get
* a kernel-tree-relative path. When that fails (e.g. O= builds where
* comp_dir is the build directory), scan for a well-known kernel
* top-level directory name in the path to recover the relative path.
* Fall back to the basename as a last resort.
*
* For relative paths (common in modules), libdw may produce a bogus
* doubled path like "net/foo/bar.c/net/foo/bar.c" due to ET_REL DWARF
* quirks. Detect and strip such duplicates.
*/
static const char *make_relative(const char *path, const char *comp_dir)
{
const char *p;
if (path[0] == '/') {
/* Try comp_dir prefix from DWARF */
if (comp_dir) {
size_t len = strlen(comp_dir);
if (!strncmp(path, comp_dir, len) && path[len] == '/') {
const char *rel = path + len + 1;
/*
* If comp_dir pointed to a subdirectory
* (e.g. arch/parisc/kernel) rather than
* the tree root, stripping it leaves a
* bare filename. Fall through to the
* kernel_dirs scan so we recover the full
* relative path instead.
*/
if (strchr(rel, '/'))
return rel;
}
}
/*
* comp_dir prefix didn't help — either it didn't match
* or it was too specific and left a bare filename.
* Scan for a known kernel top-level directory component
* to find where the relative path starts. This handles
* O= builds and arches where comp_dir is a subdirectory.
*/
for (p = path + 1; *p; p++) {
if (*(p - 1) == '/') {
for (unsigned int i = 0; i < sizeof(kernel_dirs) /
sizeof(kernel_dirs[0]); i++) {
if (!strncmp(p, kernel_dirs[i],
strlen(kernel_dirs[i])))
return p;
}
}
}
/* Fall back to basename */
p = strrchr(path, '/');
return p ? p + 1 : path;
}
/*
* Relative path — check for duplicated-path quirk from libdw
* on ET_REL files (e.g., "a/b.c/a/b.c" → "a/b.c").
*/
{
size_t len = strlen(path);
size_t mid = len / 2;
if (len > 1 && path[mid] == '/' &&
!memcmp(path, path + mid + 1, mid))
return path + mid + 1;
}
/*
* Bare filename with no directory component — try to recover the
* relative path using comp_dir. Some toolchains/elfutils combos
* produce bare filenames where comp_dir holds the source directory.
* Construct the absolute path and run the kernel_dirs scan.
*/
if (!strchr(path, '/') && comp_dir && comp_dir[0] == '/') {
static char buf[PATH_MAX];
snprintf(buf, sizeof(buf), "%s/%s", comp_dir, path);
for (p = buf + 1; *p; p++) {
if (*(p - 1) == '/') {
for (unsigned int i = 0; i < sizeof(kernel_dirs) /
sizeof(kernel_dirs[0]); i++) {
if (!strncmp(p, kernel_dirs[i],
strlen(kernel_dirs[i])))
return p;
}
}
}
}
return path;
}
static int compare_entries(const void *a, const void *b)
{
const struct line_entry *ea = a;
const struct line_entry *eb = b;
if (ea->offset != eb->offset)
return ea->offset < eb->offset ? -1 : 1;
if (ea->file_id != eb->file_id)
return ea->file_id < eb->file_id ? -1 : 1;
if (ea->line != eb->line)
return ea->line < eb->line ? -1 : 1;
return 0;
}
static unsigned long long find_text_addr(Elf *elf)
{
size_t nsyms, i;
Elf_Scn *scn = NULL;
GElf_Shdr shdr;
while ((scn = elf_nextscn(elf, scn)) != NULL) {
Elf_Data *data;
if (!gelf_getshdr(scn, &shdr))
continue;
if (shdr.sh_type != SHT_SYMTAB)
continue;
data = elf_getdata(scn, NULL);
if (!data)
continue;
nsyms = shdr.sh_size / shdr.sh_entsize;
for (i = 0; i < nsyms; i++) {
GElf_Sym sym;
const char *name;
if (!gelf_getsym(data, i, &sym))
continue;
name = elf_strptr(elf, shdr.sh_link, sym.st_name);
if (name && !strcmp(name, "_text"))
return sym.st_value;
}
}
fprintf(stderr, "Cannot find _text symbol\n");
exit(1);
}
static void find_text_section_range(Elf *elf)
{
Elf_Scn *scn = NULL;
GElf_Shdr shdr;
size_t shstrndx;
if (elf_getshdrstrndx(elf, &shstrndx) != 0)
return;
while ((scn = elf_nextscn(elf, scn)) != NULL) {
const char *name;
if (!gelf_getshdr(scn, &shdr))
continue;
name = elf_strptr(elf, shstrndx, shdr.sh_name);
if (name && !strcmp(name, ".text")) {
text_section_start = shdr.sh_addr;
text_section_end = shdr.sh_addr + shdr.sh_size;
return;
}
}
}
/*
* Apply .rela.debug_line relocations to a mutable copy of .debug_line data.
*
* elfutils libdw (through at least 0.194) does NOT apply relocations for
* ET_REL files when using dwarf_begin_elf(). The internal libdwfl layer
* does this via __libdwfl_relocate(), but that API is not public.
*
* For DWARF5, the .debug_line file name table uses DW_FORM_line_strp
* references into .debug_line_str. Without relocation, all these offsets
* resolve to 0 (or garbage), causing dwarf_linesrc()/dwarf_filesrc() to
* return wrong filenames (typically the comp_dir for every file).
*
* This function applies the relocations manually so that the patched
* .debug_line data can be fed to dwarf_begin_elf() and produce correct
* results.
*
* See elfutils bug https://sourceware.org/bugzilla/show_bug.cgi?id=31447
* A fix (dwelf_elf_apply_relocs) was proposed but not yet merged as of
* elfutils 0.194: https://sourceware.org/pipermail/elfutils-devel/2024q3/007388.html
*/
/*
* Determine the relocation type for a 32-bit absolute reference
* on the given architecture. Returns 0 if unknown.
*/
static unsigned int r_type_abs32(unsigned int e_machine)
{
switch (e_machine) {
case EM_X86_64: return R_X86_64_32;
case EM_386: return R_386_32;
case EM_AARCH64: return R_AARCH64_ABS32;
case EM_ARM: return R_ARM_ABS32;
case EM_RISCV: return R_RISCV_32;
case EM_S390: return R_390_32;
case EM_MIPS: return R_MIPS_32;
case EM_PPC64: return R_PPC64_ADDR32;
case EM_PPC: return R_PPC_ADDR32;
case EM_LOONGARCH: return R_LARCH_32;
case EM_PARISC: return R_PARISC_DIR32;
default: return 0;
}
}
static void apply_debug_line_relocations(Elf *elf)
{
Elf_Scn *scn = NULL;
Elf_Scn *debug_line_scn = NULL;
Elf_Scn *rela_debug_line_scn = NULL;
Elf_Scn *symtab_scn = NULL;
GElf_Shdr shdr;
GElf_Ehdr ehdr;
unsigned int abs32_type;
size_t shstrndx;
Elf_Data *dl_data, *rela_data, *sym_data;
GElf_Shdr rela_shdr, sym_shdr;
size_t nrels, i;
if (gelf_getehdr(elf, &ehdr) == NULL)
return;
abs32_type = r_type_abs32(ehdr.e_machine);
if (!abs32_type)
return;
if (elf_getshdrstrndx(elf, &shstrndx) != 0)
return;
/* Find the relevant sections */
while ((scn = elf_nextscn(elf, scn)) != NULL) {
const char *name;
if (!gelf_getshdr(scn, &shdr))
continue;
name = elf_strptr(elf, shstrndx, shdr.sh_name);
if (!name)
continue;
if (!strcmp(name, ".debug_line"))
debug_line_scn = scn;
else if (!strcmp(name, ".rela.debug_line"))
rela_debug_line_scn = scn;
else if (shdr.sh_type == SHT_SYMTAB)
symtab_scn = scn;
}
if (!debug_line_scn || !rela_debug_line_scn || !symtab_scn)
return;
dl_data = elf_getdata(debug_line_scn, NULL);
rela_data = elf_getdata(rela_debug_line_scn, NULL);
sym_data = elf_getdata(symtab_scn, NULL);
if (!dl_data || !rela_data || !sym_data)
return;
if (!gelf_getshdr(rela_debug_line_scn, &rela_shdr))
return;
if (!gelf_getshdr(symtab_scn, &sym_shdr))
return;
nrels = rela_shdr.sh_size / rela_shdr.sh_entsize;
for (i = 0; i < nrels; i++) {
GElf_Rela rela;
GElf_Sym sym;
unsigned int r_type;
size_t r_sym;
uint32_t value;
if (!gelf_getrela(rela_data, i, &rela))
continue;
r_type = GELF_R_TYPE(rela.r_info);
r_sym = GELF_R_SYM(rela.r_info);
/* Only handle the 32-bit absolute reloc for this arch */
if (r_type != abs32_type)
continue;
if (!gelf_getsym(sym_data, r_sym, &sym))
continue;
/* Relocated value = sym.st_value + addend */
value = (uint32_t)(sym.st_value + rela.r_addend);
/* Patch the .debug_line data at the relocation offset */
if (rela.r_offset + 4 <= dl_data->d_size)
memcpy((char *)dl_data->d_buf + rela.r_offset,
&value, sizeof(value));
}
}
static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
{
Dwarf_Off off = 0, next_off;
size_t hdr_size;
while (dwarf_nextcu(dwarf, off, &next_off, &hdr_size,
NULL, NULL, NULL) == 0) {
Dwarf_Die cudie;
Dwarf_Lines *lines;
size_t nlines;
Dwarf_Attribute attr;
const char *comp_dir = NULL;
if (!dwarf_offdie(dwarf, off + hdr_size, &cudie))
goto next;
if (dwarf_attr(&cudie, DW_AT_comp_dir, &attr))
comp_dir = dwarf_formstring(&attr);
if (dwarf_getsrclines(&cudie, &lines, &nlines) != 0)
goto next;
for (size_t i = 0; i < nlines; i++) {
Dwarf_Line *line = dwarf_onesrcline(lines, i);
Dwarf_Addr addr;
const char *src;
const char *rel;
unsigned int file_id, loffset;
int lineno;
if (!line)
continue;
if (dwarf_lineaddr(line, &addr) != 0)
continue;
if (dwarf_lineno(line, &lineno) != 0)
continue;
if (lineno == 0)
continue;
src = dwarf_linesrc(line, NULL, NULL);
if (!src)
continue;
if (addr < text_addr)
continue;
/*
* In module mode, keep only .text addresses.
* In ET_REL .ko files, .text, .init.text and
* .exit.text all have sh_addr == 0 and therefore
* overlapping address ranges. Explicitly check
* against the .text bounds.
*/
if (module_mode && text_section_end > text_section_start &&
(addr < text_section_start || addr >= text_section_end))
continue;
{
unsigned long long raw_offset = addr - text_addr;
if (raw_offset > UINT_MAX) {
skipped_overflow++;
continue;
}
loffset = (unsigned int)raw_offset;
}
rel = make_relative(src, comp_dir);
file_id = find_or_add_file(rel);
add_entry(loffset, file_id, (unsigned int)lineno);
}
next:
off = next_off;
}
}
static void deduplicate(void)
{
unsigned int i, j;
if (num_entries < 2)
return;
/* Sort by offset, then file_id, then line for stability */
qsort(entries, num_entries, sizeof(*entries), compare_entries);
/*
* Remove duplicate entries:
* - Same offset: keep first (deterministic from stable sort keys)
* - Same file:line as previous kept entry: redundant for binary
* search -- any address between them resolves to the earlier one
*/
j = 0;
for (i = 1; i < num_entries; i++) {
if (entries[i].offset == entries[j].offset)
continue;
if (entries[i].file_id == entries[j].file_id &&
entries[i].line == entries[j].line)
continue;
j++;
if (j != i)
entries[j] = entries[i];
}
num_entries = j + 1;
}
/*
* Emit the LEB128 delta-compressed data stream for one block.
* Uses .uleb128/.sleb128 assembler directives for encoding.
*/
static void emit_block_data(unsigned int block)
{
unsigned int base = block * LINEINFO_BLOCK_ENTRIES;
unsigned int count = num_entries - base;
if (count > LINEINFO_BLOCK_ENTRIES)
count = LINEINFO_BLOCK_ENTRIES;
/* Entry 0: file_id, line (both unsigned) */
printf("\t.uleb128 %u\n", entries[base].file_id);
printf("\t.uleb128 %u\n", entries[base].line);
/* Entries 1..N: addr_delta (unsigned), file/line deltas (signed) */
for (unsigned int i = 1; i < count; i++) {
unsigned int idx = base + i;
printf("\t.uleb128 %u\n",
entries[idx].offset - entries[idx - 1].offset);
printf("\t.sleb128 %d\n",
(int)entries[idx].file_id - (int)entries[idx - 1].file_id);
printf("\t.sleb128 %d\n",
(int)entries[idx].line - (int)entries[idx - 1].line);
}
}
static void compute_file_offsets(void)
{
unsigned int offset = 0;
for (unsigned int i = 0; i < num_files; i++) {
files[i].str_offset = offset;
offset += strlen(files[i].name) + 1;
}
}
static void print_escaped_asciz(const char *s)
{
printf("\t.asciz \"");
for (; *s; s++) {
if (*s == '"' || *s == '\\')
putchar('\\');
putchar(*s);
}
printf("\"\n");
}
static void output_assembly(void)
{
unsigned int num_blocks;
num_blocks = num_entries ?
(num_entries + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0;
printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
printf("/*\n");
printf(" * Automatically generated by scripts/gen_lineinfo\n");
printf(" * Do not edit.\n");
printf(" */\n\n");
printf("\t.section .rodata, \"a\"\n\n");
/* Number of entries */
printf("\t.globl lineinfo_num_entries\n");
printf("\t.balign 4\n");
printf("lineinfo_num_entries:\n");
printf("\t.long %u\n\n", num_entries);
/* Number of files */
printf("\t.globl lineinfo_num_files\n");
printf("\t.balign 4\n");
printf("lineinfo_num_files:\n");
printf("\t.long %u\n\n", num_files);
/* Number of blocks */
printf("\t.globl lineinfo_num_blocks\n");
printf("\t.balign 4\n");
printf("lineinfo_num_blocks:\n");
printf("\t.long %u\n\n", num_blocks);
/* Block first-addresses for binary search */
printf("\t.globl lineinfo_block_addrs\n");
printf("\t.balign 4\n");
printf("lineinfo_block_addrs:\n");
for (unsigned int i = 0; i < num_blocks; i++)
printf("\t.long 0x%x\n", entries[i * LINEINFO_BLOCK_ENTRIES].offset);
/* Block byte offsets into compressed stream */
printf("\t.globl lineinfo_block_offsets\n");
printf("\t.balign 4\n");
printf("lineinfo_block_offsets:\n");
for (unsigned int i = 0; i < num_blocks; i++)
printf("\t.long .Lblock_%u - lineinfo_data\n", i);
/* Compressed data size */
printf("\t.globl lineinfo_data_size\n");
printf("\t.balign 4\n");
printf("lineinfo_data_size:\n");
printf("\t.long .Ldata_end - lineinfo_data\n\n");
/* Compressed data stream */
printf("\t.globl lineinfo_data\n");
printf("lineinfo_data:\n");
for (unsigned int i = 0; i < num_blocks; i++) {
printf(".Lblock_%u:\n", i);
emit_block_data(i);
}
printf(".Ldata_end:\n\n");
/* File string offset table */
printf("\t.globl lineinfo_file_offsets\n");
printf("\t.balign 4\n");
printf("lineinfo_file_offsets:\n");
for (unsigned int i = 0; i < num_files; i++)
printf("\t.long %u\n", files[i].str_offset);
/* Filenames size */
printf("\t.globl lineinfo_filenames_size\n");
printf("\t.balign 4\n");
printf("lineinfo_filenames_size:\n");
printf("\t.long .Lfilenames_end - lineinfo_filenames\n\n");
/* Concatenated NUL-terminated filenames */
printf("\t.globl lineinfo_filenames\n");
printf("lineinfo_filenames:\n");
for (unsigned int i = 0; i < num_files; i++)
print_escaped_asciz(files[i].name);
printf(".Lfilenames_end:\n");
}
static void output_module_assembly(void)
{
unsigned int num_blocks;
num_blocks = num_entries ?
(num_entries + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0;
printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
printf("/*\n");
printf(" * Automatically generated by scripts/gen_lineinfo --module\n");
printf(" * Do not edit.\n");
printf(" */\n\n");
printf("\t.section .mod_lineinfo, \"a\"\n\n");
/*
* Header -- offsets and sizes are assembler expressions so the
* layout is self-describing without manual C arithmetic.
*/
printf(".Lhdr:\n");
printf("\t.balign 4\n");
printf("\t.long %u\t\t\t\t/* num_entries */\n", num_entries);
printf("\t.long %u\t\t\t\t/* num_blocks */\n", num_blocks);
printf("\t.long %u\t\t\t\t/* num_files */\n", num_files);
printf("\t.long .Lblk_addrs - .Lhdr\t\t/* blocks_offset */\n");
printf("\t.long .Lblk_offsets_end - .Lblk_addrs\t/* blocks_size */\n");
printf("\t.long .Ldata - .Lhdr\t\t\t/* data_offset */\n");
printf("\t.long .Ldata_end - .Ldata\t\t/* data_size */\n");
printf("\t.long .Lfile_offsets - .Lhdr\t\t/* files_offset */\n");
printf("\t.long .Lfile_offsets_end - .Lfile_offsets /* files_size */\n");
printf("\t.long .Lfilenames - .Lhdr\t\t/* filenames_offset */\n");
printf("\t.long .Lfilenames_end - .Lfilenames\t/* filenames_size */\n");
printf("\t.long 0\t\t\t\t\t/* reserved */\n\n");
/* block_addrs[] */
printf(".Lblk_addrs:\n");
for (unsigned int i = 0; i < num_blocks; i++)
printf("\t.long 0x%x\n", entries[i * LINEINFO_BLOCK_ENTRIES].offset);
/* block_offsets[] */
printf(".Lblk_offsets:\n");
for (unsigned int i = 0; i < num_blocks; i++)
printf("\t.long .Lblock_%u - .Ldata\n", i);
printf(".Lblk_offsets_end:\n\n");
/* compressed data stream */
printf(".Ldata:\n");
for (unsigned int i = 0; i < num_blocks; i++) {
printf(".Lblock_%u:\n", i);
emit_block_data(i);
}
printf(".Ldata_end:\n");
/* file_offsets[] */
printf("\t.balign 4\n");
printf(".Lfile_offsets:\n");
for (unsigned int i = 0; i < num_files; i++)
printf("\t.long %u\n", files[i].str_offset);
printf(".Lfile_offsets_end:\n\n");
/* filenames[] */
printf(".Lfilenames:\n");
for (unsigned int i = 0; i < num_files; i++)
print_escaped_asciz(files[i].name);
printf(".Lfilenames_end:\n");
}
int main(int argc, char *argv[])
{
int fd;
Elf *elf;
Dwarf *dwarf;
unsigned long long text_addr;
if (argc >= 2 && !strcmp(argv[1], "--module")) {
module_mode = 1;
argv++;
argc--;
}
if (argc != 2) {
fprintf(stderr, "Usage: %s [--module] <ELF file>\n", argv[0]);
return 1;
}
/*
* For module mode, open O_RDWR so we can apply debug section
* relocations to the in-memory ELF data. The modifications
* are NOT written back to disk (no elf_update() call).
*/
fd = open(argv[1], module_mode ? O_RDWR : O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Cannot open %s: %s\n", argv[1],
strerror(errno));
return 1;
}
elf_version(EV_CURRENT);
elf = elf_begin(fd, module_mode ? ELF_C_RDWR : ELF_C_READ, NULL);
if (!elf) {
fprintf(stderr, "elf_begin failed: %s\n",
elf_errmsg(elf_errno()));
close(fd);
return 1;
}
if (module_mode) {
/*
* .ko files are ET_REL after ld -r. libdw does NOT apply
* relocations for ET_REL files, so DW_FORM_line_strp
* references in .debug_line are not resolved. Apply them
* ourselves so that dwarf_linesrc() returns correct paths.
*
* DWARF addresses include the .text sh_addr. Use .text
* sh_addr as the base so offsets are .text-relative.
*/
apply_debug_line_relocations(elf);
find_text_section_range(elf);
text_addr = text_section_start;
} else {
text_addr = find_text_addr(elf);
}
dwarf = dwarf_begin_elf(elf, DWARF_C_READ, NULL);
if (!dwarf) {
fprintf(stderr, "dwarf_begin_elf failed: %s\n",
dwarf_errmsg(dwarf_errno()));
fprintf(stderr, "Is %s built with CONFIG_DEBUG_INFO?\n",
argv[1]);
elf_end(elf);
close(fd);
return 1;
}
process_dwarf(dwarf, text_addr);
if (skipped_overflow)
fprintf(stderr,
"lineinfo: warning: %u entries skipped (offset > 4 GiB from _text)\n",
skipped_overflow);
deduplicate();
compute_file_offsets();
fprintf(stderr, "lineinfo: %u entries, %u files, %u blocks\n",
num_entries, num_files,
num_entries ?
(num_entries + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0);
if (module_mode)
output_module_assembly();
else
output_assembly();
dwarf_end(dwarf);
elf_end(elf);
close(fd);
/* Cleanup */
free(entries);
for (unsigned int i = 0; i < num_files; i++)
free(files[i].name);
free(files);
return 0;
}