Files

456 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>借助 eBPF 和 BTF让用户态也能一次编译、到处运行 - bpf-developer-tutorial</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="../https://github.com/eunomia-bpf/bpf-developer-tutorial.html">https://github.com/eunomia-bpf/bpf-developer-tutorial</a></li><li class="chapter-item expanded affix "><li class="part-title">入门文档</li><li class="chapter-item expanded "><a href="../0-introduce/index.html"><strong aria-hidden="true">1.</strong> lesson 0-introduce</a></li><li class="chapter-item expanded "><a href="../1-helloworld/index.html"><strong aria-hidden="true">2.</strong> lesson 1-helloworld</a></li><li class="chapter-item expanded "><a href="../2-kprobe-unlink/index.html"><strong aria-hidden="true">3.</strong> lesson 2-kprobe-unlink</a></li><li class="chapter-item expanded "><a href="../3-fentry-unlink/index.html"><strong aria-hidden="true">4.</strong> lesson 3-fentry-unlink</a></li><li class="chapter-item expanded "><a href="../4-opensnoop/index.html"><strong aria-hidden="true">5.</strong> lesson 4-opensnoop</a></li><li class="chapter-item expanded "><a href="../5-uprobe-bashreadline/index.html"><strong aria-hidden="true">6.</strong> lesson 5-uprobe-bashreadline</a></li><li class="chapter-item expanded "><a href="../6-sigsnoop/index.html"><strong aria-hidden="true">7.</strong> lesson 6-sigsnoop</a></li><li class="chapter-item expanded "><a href="../7-execsnoop/index.html"><strong aria-hidden="true">8.</strong> lesson 7-execsnoop</a></li><li class="chapter-item expanded "><a href="../8-exitsnoop/index.html"><strong aria-hidden="true">9.</strong> lesson 8-execsnoop</a></li><li class="chapter-item expanded "><a href="../9-runqlat/index.html"><strong aria-hidden="true">10.</strong> lesson 9-runqlat</a></li><li class="chapter-item expanded "><a href="../10-hardirqs/index.html"><strong aria-hidden="true">11.</strong> lesson 10-hardirqs</a></li><li class="chapter-item expanded affix "><li class="part-title">进阶文档和示例</li><li class="chapter-item expanded "><a href="../11-bootstrap/index.html"><strong aria-hidden="true">12.</strong> lesson 11-bootstrap</a></li><li class="chapter-item expanded "><a href="../12-profile/index.html"><strong aria-hidden="true">13.</strong> lesson 12-profile</a></li><li class="chapter-item expanded "><a href="../13-tcpconnlat/index.html"><strong aria-hidden="true">14.</strong> lesson 13-tcpconnlat</a></li><li class="chapter-item expanded "><a href="../14-tcpstates/index.html"><strong aria-hidden="true">15.</strong> lesson 14-tcpstates</a></li><li class="chapter-item expanded "><a href="../15-javagc/index.html"><strong aria-hidden="true">16.</strong> lesson 15-javagc</a></li><li class="chapter-item expanded "><a href="../16-memleak/index.html"><strong aria-hidden="true">17.</strong> lesson 16-memleak</a></li><li class="chapter-item expanded "><a href="../17-biopattern/index.html"><strong aria-hidden="true">18.</strong> lesson 17-biopattern</a></li><li class="chapter-item expanded "><a href="../18-further-reading/index.html"><strong aria-hidden="true">19.</strong> lesson 18-further-reading</a></li><li class="chapter-item expanded "><a href="../19-lsm-connect/index.html"><strong aria-hidden="true">20.</strong> lesson 19-lsm-connect</a></li><li class="chapter-item expanded "><a href="../20-tc/index.html"><strong aria-hidden="true">21.</strong> lesson 20-tc</a></li><li class="chapter-item expanded "><a href="../21-xdp/index.html"><strong aria-hidden="true">22.</strong> lesson 21-xdp</a></li><li class="chapter-item expanded affix "><li class="part-title">高级主题</li><li class="chapter-item expanded "><a href="../22-android/index.html"><strong aria-hidden="true">23.</strong> 在 Android 上使用 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../30-sslsniff/index.html"><strong aria-hidden="true">24.</strong> 使用 uprobe 捕获多种库的 SSL/TLS 明文数据</a></li><li class="chapter-item expanded "><a href="../23-http/index.html"><strong aria-hidden="true">25.</strong> 使用 eBPF socket filter 或 syscall trace 追踪 HTTP 请求和其他七层协议</a></li><li class="chapter-item expanded "><a href="../29-sockops/index.html"><strong aria-hidden="true">26.</strong> 使用 sockops 加速网络请求转发</a></li><li class="chapter-item expanded "><a href="../24-hide/index.html"><strong aria-hidden="true">27.</strong> 使用 eBPF 隐藏进程或文件信息</a></li><li class="chapter-item expanded "><a href="../25-signal/index.html"><strong aria-hidden="true">28.</strong> 使用 bpf_send_signal 发送信号终止进程</a></li><li class="chapter-item expanded "><a href="../26-sudo/index.html"><strong aria-hidden="true">29.</strong> 使用 eBPF 添加 sudo 用户</a></li><li class="chapter-item expanded "><a href="../27-replace/index.html"><strong aria-hidden="true">30.</strong> 使用 eBPF 替换任意程序读取或写入的文本</a></li><li class="chapter-item expanded "><a href="../28-detach/index.html"><strong aria-hidden="true">31.</strong> BPF 的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../18-further-reading/ebpf-security.zh.html"><strong aria-hidden="true">32.</strong> eBPF 运行时的安全性与面临的挑战</a></li><li class="chapter-item expanded "><a href="../34-syscall/index.html"><strong aria-hidden="true">33.</strong> 使用 eBPF 修改系统调用参数</a></li><li class="chapter-item expanded "><a href="../35-user-ringbuf/index.html"><strong aria-hidden="true">34.</strong> eBPF开发实践使用 user ring buffer 向内核异步发送信息</a></li><li class="chapter-item expanded "><a href="../36-userspace-ebpf/index.html"><strong aria-hidden="true">35.</strong> 用户空间 eBPF 运行时:深度解析与应用实践</a></li><li class="chapter-item expanded "><a href="../37-uprobe-rust/index.html"><strong aria-hidden="true">36.</strong> 使用 uprobe 追踪 Rust 应用程序</a></li><li class="chapter-item expanded "><a href="../38-btf-uprobe/index.html" class="active"><strong aria-hidden="true">37.</strong> 借助 eBPF 和 BTF让用户态也能一次编译、到处运行</a></li><li class="chapter-item expanded affix "><li class="part-title">bcc 和 bpftrace 教程与文档</li><li class="chapter-item expanded "><a href="../bcc-documents/kernel-versions.html"><strong aria-hidden="true">38.</strong> BPF Features by Linux Kernel Version</a></li><li class="chapter-item expanded "><a href="../bcc-documents/kernel_config.html"><strong aria-hidden="true">39.</strong> Kernel Configuration for BPF Features</a></li><li class="chapter-item expanded "><a href="../bcc-documents/reference_guide.html"><strong aria-hidden="true">40.</strong> bcc Reference Guide</a></li><li class="chapter-item expanded "><a href="../bcc-documents/special_filtering.html"><strong aria-hidden="true">41.</strong> Special Filtering</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial.html"><strong aria-hidden="true">42.</strong> bcc Tutorial</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial_bcc_python_developer.html"><strong aria-hidden="true">43.</strong> bcc Python Developer Tutorial</a></li><li class="chapter-item expanded "><a href="../bpftrace-tutorial/index.html"><strong aria-hidden="true">44.</strong> bpftrace Tutorial</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">bpf-developer-tutorial</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="借助-ebpf-和-btf让用户态也能一次编译到处运行"><a class="header" href="#借助-ebpf-和-btf让用户态也能一次编译到处运行">借助 eBPF 和 BTF让用户态也能一次编译、到处运行</a></h1>
<p>在现代 Linux 系统中eBPF扩展的 Berkeley Packet Filter是一项强大而灵活的技术。它允许在内核中运行沙盒化程序类似于虚拟机环境为扩展内核功能提供了一种既安全又不会导致系统崩溃或安全风险的方法。</p>
<p>eBPF 中的 “co-re” 代表“一次编译、到处运行”。这是其关键特征之一,用于解决 eBPF 程序在不同内核版本间兼容性的主要挑战。eBPF 的 CO-RE 功能可以实现在不同的内核版本上运行同一 eBPF 程序,而无需重新编译。</p>
<p>利用 eBPF 的 Uprobe 功能,可以追踪用户空间应用程序并访问其内部数据结构。然而,用户空间应用程序的 CO-RE 实践目前尚不完善。本文将介绍一种新方法,利用 CO-RE 为用户空间应用程序确保 eBPF 程序在不同应用版本间的兼容性,从而避免了多次编译的需求。例如,在从加密流量中捕获 SSL/TLS 明文数据时,你或许不需要为每个版本的 OpenSSL 维护一个单独的 eBPF 程序。</p>
<p>为了在用户空间应用程序中实现eBPF的“一次编译、到处运行”(Co-RE)特性我们需要利用BPF类型格式(BTF)来克服传统eBPF程序的一些限制。这种方法的关键在于为用户空间程序提供与内核类似的类型信息和兼容性支持从而使得eBPF程序能够更灵活地应对不同版本的用户空间应用和库。</p>
<p>本文是eBPF开发者教程的一部分详细内容可访问<a href="https://eunomia.dev/tutorials/">https://eunomia.dev/tutorials/</a>。本文完整的代码请查看 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/38-btf-uprobe">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/38-btf-uprobe</a></p>
<h2 id="为什么我们需要co-re"><a class="header" href="#为什么我们需要co-re">为什么我们需要CO-RE</a></h2>
<ul>
<li><strong>内核依赖性</strong>传统的eBPF程序和它们被编译的特定Linux内核版本紧密耦合。这是因为它们依赖于内核的特定内部数据结构和API这些可能在内核版本间变化。</li>
<li><strong>可移植性问题</strong>如果你想在带有不同内核版本的不同Linux系统上运行一个eBPF程序你通常需要为每个内核版本重新编译eBPF程序这是一个麻烦而低效的过程。</li>
</ul>
<h3 id="co-re的解决方案"><a class="header" href="#co-re的解决方案">Co-RE的解决方案</a></h3>
<ul>
<li><strong>抽象内核依赖性</strong>Co-RE使eBPF程序更具可移植性通过使用BPF类型格式(BTF)和重定位来抽象特定的内核依赖。</li>
<li><strong>BPF类型格式BTF</strong>BTF提供了关于内核中数据结构和函数的丰富类型信息。这些元数据允许eBPF程序在运行时理解内核结构的布局。</li>
<li><strong>重定位</strong>编译支持Co-RE的eBPF程序包含在加载时解析的重定位。这些重定位根据运行内核的实际布局和地址调整程序对内核数据结构和函数的引用。</li>
</ul>
<h3 id="co-re的优点"><a class="header" href="#co-re的优点">Co-RE的优点</a></h3>
<ol>
<li><strong>编写一次,任何地方运行</strong>编译有Co-RE的eBPF程序可以在不同的内核版本上运行无需重新编译。这大大简化了在多样环境中部署和维护eBPF程序。</li>
<li><strong>安全和稳定</strong>Co-RE保持了eBPF的安全性确保程序不会导致内核崩溃遵守安全约束。</li>
<li><strong>简单的开发</strong>开发者不需要关注每个内核版本的具体情况这简化了eBPF程序的开发。</li>
</ol>
<h2 id="用户空间应用程序co-re的问题"><a class="header" href="#用户空间应用程序co-re的问题">用户空间应用程序CO-RE的问题</a></h2>
<p>eBPF也支持追踪用户空间应用程序。Uprobe是一个用户空间探针允许对用户空间程序进行动态仪表装置。探针位置包括函数入口、特定偏移和函数返回。</p>
<p>BTF是为内核设计的生成自vmlinux它可以帮助eBPF程序方便地兼容不同的内核版本。但是用户空间应用程序也需要CO-RE。例如SSL/TLS uprobe被广泛用于从加密流量中捕获明文数据。它是用用户空间库实现的如OpenSSL、GnuTLS、NSS等。用户空间应用程序和库也有各种版本如果我们需要为每个版本编译和维护eBPF程序那就会很复杂。</p>
<p>下面是一些新的工具和方法可以帮助我们为用户空间应用程序启用CO-RE。</p>
<h2 id="用户空间程序的btf"><a class="header" href="#用户空间程序的btf">用户空间程序的BTF</a></h2>
<p>这是一个简单的uprobe例子它可以捕获用户空间程序的<code>add_test</code>函数的调用和参数。你可以在<code>uprobe.bpf.c</code>中添加<code>#define BPF_NO_PRESERVE_ACCESS_INDEX</code>来确保eBPF程序可以在没有<code>struct data</code>的BTF的情况下编译。</p>
<pre><code class="language-c">#define BPF_NO_GLOBAL_DATA
#define BPF_NO_PRESERVE_ACCESS_INDEX
#include &lt;vmlinux.h&gt;
#include &lt;bpf/bpf_helpers.h&gt;
#include &lt;bpf/bpf_tracing.h&gt;
struct data {
int a;
int c;
int d;
};
SEC("uprobe/examples/btf-base:add_test")
int BPF_UPROBE(add_test, struct data *d)
{
int a = 0, c = 0;
bpf_probe_read_user(&amp;a, sizeof(a), &amp;d-&gt;a);
bpf_probe_read_user(&amp;c, sizeof(c), &amp;d-&gt;c);
bpf_printk("add_test(&amp;d) %d + %d = %d\n", a, c, a + c);
return a + c;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
</code></pre>
<p>然后,我们有两个不同版本的用户空间程序,<code>examples/btf-base</code><code>examples/btf-base-new</code>。两个版本中的struct <code>data</code>是不同的。</p>
<p><code>examples/btf-base</code></p>
<pre><code class="language-c">// use a different struct
struct data {
int a;
int c;
int d;
};
int add_test(struct data *d) {
return d-&gt;a + d-&gt;c;
}
int main(int argc, char **argv) {
struct data d = {1, 3, 4};
printf("add_test(&amp;d) = %d\n", add_test(&amp;d));
return 0;
}
</code></pre>
<p><code>examples/btf-base-new</code></p>
<pre><code class="language-c">struct data {
int a;
int b;
int c;
int d;
};
int add_test(struct data *d) {
return d-&gt;a + d-&gt;c;
}
int main(int argc, char **argv) {
struct data d = {1, 2, 3, 4};
printf("add_test(&amp;d) = %d\n", add_test(&amp;d));
return 0;
}
</code></pre>
<p>我们可以使用pahole和clang来生成每个版本的btf。制作示例并生成btf:</p>
<pre><code class="language-sh">make -C example # it's like: pahole --btf_encode_detached base.btf btf-base.o
</code></pre>
<p>然后我们执行eBPF程序和用户空间程序。 对于 <code>btf-base</code></p>
<pre><code class="language-sh">sudo ./uprobe examples/btf-base
</code></pre>
<p>也是用户空间程序:</p>
<pre><code class="language-console">$ examples/btf-base
add_test(&amp;d) = 4
</code></pre>
<p>我们将看到:</p>
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe\
&lt;...&gt;-25458 [000] ...11 27694.081465: bpf_trace_printk: add_test(&amp;d) 1 + 3 = 4
</code></pre>
<p>对于 <code>btf-base-new</code></p>
<pre><code class="language-sh">sudo ./uprobe examples/btf-base-new
</code></pre>
<p>同时也是用户空间程序:</p>
<pre><code class="language-console">$ examples/btf-base-new
add_test(&amp;d) = 4
</code></pre>
<p>但我们可以看到:</p>
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe\
&lt;...&gt;-25809 [001] ...11 27828.314224: bpf_trace_printk: add_test(&amp;d) 1 + 2 = 3
</code></pre>
<p>结果是不同的因为两个版本中的struct <code>data</code>是不同的。eBPF程序无法与不同版本的用户空间程序兼容我们获取到了错误的结构体偏移量也会导致我们追踪失败。</p>
<h2 id="使用用户空间程序的btf"><a class="header" href="#使用用户空间程序的btf">使用用户空间程序的BTF</a></h2>
<p><code>uprobe.bpf.c</code>中注释掉<code>#define BPF_NO_PRESERVE_ACCESS_INDEX</code> 以确保eBPF程序可以以<code>struct data</code>的BTF编译。</p>
<pre><code class="language-c">#define BPF_NO_GLOBAL_DATA
// #define BPF_NO_PRESERVE_ACCESS_INDEX
#include &lt;vmlinux.h&gt;
#include &lt;bpf/bpf_helpers.h&gt;
#include &lt;bpf/bpf_tracing.h&gt;
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record)
#endif
struct data {
int a;
int c;
int d;
};
#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute pop
#endif
SEC("uprobe/examples/btf-base:add_test")
int BPF_UPROBE(add_test, struct data *d)
{
int a = 0, c = 0;
bpf_probe_read_user(&amp;a, sizeof(a), &amp;d-&gt;a);
bpf_probe_read_user(&amp;c, sizeof(c), &amp;d-&gt;c);
bpf_printk("add_test(&amp;d) %d + %d = %d\n", a, c, a + c);
return a + c;
}
char LICENSE[] SEC("license") = "Dual BSD/GPL";
</code></pre>
<p><code>struct data</code>的记录在eBPF程序中被保留下来。然后我们可以使用 <code>btf-base.btf</code>来编译eBPF程序。</p>
<p>将用户btf与内核btf合并这样我们就有了一个完整的内核和用户空间的btf:</p>
<pre><code class="language-sh">./merge-btf /sys/kernel/btf/vmlinux examples/base.btf target-base.btf
</code></pre>
<p>然后我们使用用户空间程序执行eBPF程序。 对于 <code>btf-base</code></p>
<pre><code class="language-console">$ sudo ./uprobe examples/btf-base target-base.btf
...
libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -&gt; 0
libbpf: prog 'add_test': relo #2: &lt;byte_off&gt; [7] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: matching candidate #0 &lt;byte_off&gt; [133110] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 4 -&gt; 4
...
</code></pre>
<p>执行用户空间程序并获取结果:</p>
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe
[sudo] password for yunwei37:
&lt;...&gt;-26740 [001] ...11 28180.156220: bpf_trace_printk: add_test(&amp;d) 1 + 3 = 4
</code></pre>
<p>还可以对另一个版本的用户空间程序<code>btf-base-new</code>做同样的操作:</p>
<pre><code class="language-console">$ ./merge-btf /sys/kernel/btf/vmlinux examples/base-new.btf target-base-new.btf
$ sudo ./uprobe examples/btf-base-new target-base-new.btf
....
libbpf: sec 'uprobe/examples/btf-base:add_test': found 3 CO-RE relocations
libbpf: CO-RE relocating [2] struct pt_regs: found target candidate [357] struct pt_regs in [vmlinux]
libbpf: prog 'add_test': relo #0: &lt;byte_off&gt; [2] struct pt_regs.di (0:14 @ offset 112)
libbpf: prog 'add_test': relo #0: matching candidate #0 &lt;byte_off&gt; [357] struct pt_regs.di (0:14 @ offset 112)
libbpf: prog 'add_test': relo #0: patched insn #0 (LDX/ST/STX) off 112 -&gt; 112
libbpf: CO-RE relocating [7] struct data: found target candidate [133110] struct data in [vmlinux]
libbpf: prog 'add_test': relo #1: &lt;byte_off&gt; [7] struct data.a (0:0 @ offset 0)
libbpf: prog 'add_test': relo #1: matching candidate #0 &lt;byte_off&gt; [133110] struct data.a (0:0 @ offset 0)
libbpf: prog 'add_test': relo #1: patched insn #4 (ALU/ALU64) imm 0 -&gt; 0
libbpf: prog 'add_test': relo #2: &lt;byte_off&gt; [7] struct data.c (0:1 @ offset 4)
libbpf: prog 'add_test': relo #2: matching candidate #0 &lt;byte_off&gt; [133110] struct data.c (0:2 @ offset 8)
libbpf: prog 'add_test': relo #2: patched insn #11 (ALU/ALU64) imm 4 -&gt; 8
libbpf: elf: symbol address match for 'add_test' in 'examples/btf-base-new': 0x1140
Successfully started! Press Ctrl+C to stop.
</code></pre>
<p>结果是正确的:</p>
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe
[sudo] password for yunwei37:
&lt;...&gt;-26740 [001] ...11 28180.156220: bpf_trace_printk: add_test(&amp;d) 1 + 3 = 4
</code></pre>
<p>我们的 eBPF 追踪程序也几乎不需要进行任何修改,只需要把包含 kernel 和用户态结构体偏移量的 BTF 加载进来即可。这和旧版本内核上没有 btf 信息的使用方式是一样的:</p>
<pre><code class="language-c"> LIBBPF_OPTS(bpf_object_open_opts , opts,
);
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
if (argc != 3 &amp;&amp; argc != 2) {
fprintf(stderr, "Usage: %s &lt;example-name&gt; [&lt;external-btf&gt;]\n", argv[0]);
return 1;
}
if (argc == 3)
opts.btf_custom_path = argv[2];
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Cleaner handling of Ctrl-C */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* Load and verify BPF application */
skel = uprobe_bpf__open_opts(&amp;opts);
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
</code></pre>
<p>实际上btf 实现重定向需要两个部分,一个是 bpf 程序带的编译时的 btf 信息,一个是内核的 btf 信息。在实际加载 ebpf 程序的时候libbpf 会根据当前内核上准确的 btf 信息,来修改可能存在错误的 ebpf 指令,确保在不同内核版本上能够兼容。</p>
<p>有趣的是,实际上 libbpf 并不区分这些 btf 信息来自用户态程序还是内核,因此我们只要把用户态的重定向信息一起提供给 libbpf 进行重定向,问题就解决了。</p>
<p>本文的工具和完整的代码在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/38-btf-uprobe">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/38-btf-uprobe</a> 开源。</p>
<h2 id="结论"><a class="header" href="#结论">结论</a></h2>
<ul>
<li><strong>灵活性和兼容性</strong>在用户空间eBPF程序中使用 BTF 大大增强了它们在不同版本的用户空间应用程序和库之间的灵活性和兼容性。</li>
<li><strong>简化了复杂性</strong>这种方法显著减少了维护不同版本的用户空间应用程序的eBPF程序的复杂性因为它消除了需要多个程序版本的需要。</li>
<li><strong>更广泛的应用</strong>这种方法在性能监控、安全和用户空间应用程序的调试等方面也可能能有更广泛的应用。bpftimehttps://github.com/eunomia-bpf/bpftime 是一个开源的基于 LLVM JIT/AOT 的用户态 eBPF 运行时,它可以在用户态运行 eBPF 程序,和内核态的 eBPF 兼容。它在支持 uprobe、syscall trace 和一般的插件扩展的同时,避免了内核态和用户态之间的上下文切换,从而提高了 uprobe 程序的执行效率。借助 libbpf 和 btf 的支持bpftime 也可以更加动态的扩展用户态应用程序,实现在不同用户态程序版本之间的兼容性。</li>
</ul>
<p>这个示例展示了 eBPF 在实践中可以将其强大的 CO-RE 功能扩展到更动态地处理用户空间应用的不同版本变化。</p>
<p>如果你想了解更多关于eBPF知识和实践你可以访问我们的教程代码库<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a>或者网站<a href="https://eunomia.dev/tutorials/">https://eunomia.dev/tutorials/</a>获得更多示例和完整教程。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../37-uprobe-rust/index.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../bcc-documents/kernel-versions.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../37-uprobe-rust/index.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../bcc-documents/kernel-versions.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>