Files
bpf-developer-tutorial/38-btf-uprobe/index.html

430 lines
31 KiB
HTML
Raw 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程序方便地兼容不同的内核版本。</p>
<p>但是用户空间应用程序也需要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(&quot;uprobe/examples/btf-base:add_test&quot;)
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(&quot;add_test(&amp;d) %d + %d = %d\n&quot;, a, c, a + c);
return a + c;
}
char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
</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(&quot;add_test(&amp;d) = %d\n&quot;, 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(&quot;add_test(&amp;d) = %d\n&quot;, 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(&quot;uprobe/examples/btf-base:add_test&quot;)
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(&quot;add_test(&amp;d) %d + %d = %d\n&quot;, a, c, a + c);
return a + c;
}
char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
</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>本文的工具和完整的代码在 <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>虽然你的例子关注于SSL/TLS监控但是这种方法在性能监控、安全和用户空间应用程序的调试等方面有更广泛的应用。</li>
</ul>
<p>这个示例展示了eBPF在实践中的重要进步将其强大的功能扩展到更动态地处理用户空间应用程序在Linux环境中。对于处理现代Linux系统复杂性的软件工程师和系统管理员来说这是一个引人注目的解决方案。</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>