Linux perf is an essential tool for performance troubleshooting. One of its core functionalities is perf record. Here’s an example:

sudo perf record -g -- dd if=/dev/zero of=/dev/null count=1024 bs=1M

Output:

1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.0760135 s, 14.1 GB/s
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.062 MB perf.data (314 samples) ]

This command collects samples and saves them to a file named perf.data in the current working directory. You can open it with:

sudo perf report

If you omit sudo, you won’t be able to open the file—because perf.data is owned by root (since perf record was invoked with sudo). So far, everything looks good: most kernel symbols are resolved properly. This is because all steps were performed with root privileges, avoiding the common permission-related symbol resolution issues.

What Happens Without Root?

Sometimes, we want to automate processing of perf.data using a script. It might be tempting to simply change the ownership of perf.data to a non-root user. However, when you do that, perf report will show unresolved addresses instead of symbols:

+   98.92%     0.00%  dd       [kernel.kallsyms]     [k] 0xffffffff9d200156
+   98.92%     0.00%  dd       [kernel.kallsyms]     [k] 0xffffffff9d1cacc6
[...]

In this output, [kernel.kallsyms] is a pseudo shared object representing all kernel symbols. As you may have guessed, this happens because perf is unable to access certain sensitive files that are only readable by root.

So, what exactly is missing? From Brendan Gregg’s blog, we know that the kernel must be compiled with CONFIG_KALLSYMS=y to allow symbol resolution. You can check this with:

sudo grep "CONFIG_KALLSYMS" /boot/config-$(uname -r)

This is enabled by default on Ubuntu 22.04, as expected. But then, why are symbols still missing?

Where Kernel Symbols Live

Kernel symbols are exposed to userspace through the virtual file /proc/kallsyms. While this file is readable by normal users, the access is limited. Try:

tail /proc/kallsyms

Sample output:

0000000000000000 d pmt_pci_driver	[intel_pmt]
0000000000000000 t pmt_pci_driver_exit	[intel_pmt]
0000000000000000 r pmt_pci_ids	[intel_pmt]
0000000000000000 r tgl_info	[intel_pmt]
0000000000000000 r dg1_info	[intel_pmt]
0000000000000000 d dg1_capabilities	[intel_pmt]
0000000000000000 d dg1_telemetry	[intel_pmt]
0000000000000000 d __this_module	[intel_pmt]
0000000000000000 t cleanup_module	[intel_pmt]
0000000000000000 r __mod_pci__pmt_pci_ids_device_table	[intel_pmt]

As you can see, the addresses are all zeroed out. This is a security measure to prevent kernel address leaks. Only the root user has full access to this file. Since both perf report and perf script depend on /proc/kallsyms for symbol resolution, running them without root privileges results in missing kernel symbols.

Accessing Kernel Symbols Without Root

Is it possible to resolve kernel symbols without root? Yes—but only if you adjust some kernel settings. You need to disable two protections:

sudo sysctl -w kernel.kptr_restrict=0
sudo sysctl -w kernel.perf_event_paranoid=0

A Note on KASLR and Reproducibility

Modern Linux distributions enable Kernel Address Space Layout Randomization (KASLR) by default. This means the addresses of kernel symbols can vary across reboots. To verify it's enabled, try:

sysctl kernel.randomize_va_space

Example output:

kernel.randomize_va_space = 2

To ensure reproducibility when analyzing perf.data, you should take a snapshot of the kernel symbol table at the time of recording:

sudo cat /proc/kallsyms > kallsyms.snapshot

Then, when running perf report or perf script, use the --kallsyms option:

perf report --kallsyms=kallsyms.snapshot