Skip to content

Commit efb84fc

Browse files
committed
Simply use backtrace-rs to look up symbols
1 parent 414f242 commit efb84fc

3 files changed

Lines changed: 48 additions & 180 deletions

File tree

README.md

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,41 @@ The microchassis is all about increasing the observability of Rust binaries.
99
Example intergration with `hyper` endpoint:
1010

1111
```rust
12-
use microchassis::profiling::jeprof;
12+
use microchassis::profiling::{jeprof, mallctl};
1313
```
1414

1515
```rust
16-
let symbol_table = Arc::new(jeprof::SymbolTable::load()?);
17-
let make_service = hyper::service::make_service_fn(move |_conn| {
18-
let symbol_table = Arc::clone(&symbol_table);
19-
let service = hyper::service::service_fn(move |req| handle(Arc::clone(&symbol_table), req));
20-
async move { Ok::<_, io::Error>(service) }
21-
});
22-
let addr = SocketAddr::from(([127, 0, 0, 1], 12345));
23-
tokio::spawn(async move { hyper::Server::bind(&addr).serve(make_service).await });
16+
std::thread::Builder::new().name("pprof".to_string()).spawn(move || {
17+
mallctl::set_thread_active(false).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
18+
let rt = tokio::runtime::Builder::new_current_thread()
19+
.enable_all()
20+
.build()
21+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
22+
let make_service = hyper::service::make_service_fn(move |_conn| {
23+
let service = hyper::service::service_fn(move |req| handle(req));
24+
async move { Ok::<_, io::Error>(service) }
25+
});
26+
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
27+
if let Err(e) =
28+
rt.block_on(async move { hyper::Server::bind(&addr).serve(make_service).await })
29+
{
30+
Err(io::Error::new(io::ErrorKind::Other, e))
31+
} else {
32+
Ok(())
33+
}
34+
})?;
2435
```
2536

2637
```rust
27-
async fn handle(
28-
symbol_table: Arc<jeprof::SymbolTable>,
29-
req: hyper::Request<hyper::Body>,
30-
) -> io::Result<hyper::Response<hyper::Body>> {
38+
async fn handle(req: hyper::Request<hyper::Body>) -> io::Result<hyper::Response<hyper::Body>> {
3139
let (parts, body) = req.into_parts();
32-
let body = hyper::body::to_bytes(body).await?;
40+
let body =
41+
hyper::body::to_bytes(body).await.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
3342
let req = hyper::Request::from_parts(parts, body.into());
3443

35-
let resp = jeprof::router(symbol_table.as_ref(), req)?;
44+
let resp = jeprof::router(req).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
3645

37-
let (parts, body) = resp.into_parts();
38-
let body = hyper::Body::from(body);
39-
let resp = hyper::Response::from_parts(parts, body);
46+
let resp = resp.map(hyper::Body::from);
4047

4148
Ok(resp)
4249
}
@@ -51,9 +58,10 @@ strip = false
5158
```
5259

5360
Disable ASLR if necessary or install the `disable_aslr` helper tool.
61+
On Linux you should prefer `setarch -R`.
5462

5563
```shell
56-
cargo install microchassis
64+
cargo install microchassis --features=disable_aslr
5765
```
5866

5967
Use package manager to install `jeprof` or download from

microchassis/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ exclude = [".gitignore", ".github", "target"]
1010
rust-version = "1.65"
1111

1212
[dependencies]
13+
backtrace = { version = "0.3", optional = true }
1314
http = "0.2"
1415
lazy_static = "1"
1516
libc = { version = "0.2", optional = true }
16-
rustc-demangle = "0.1"
1717
tempfile = "3"
1818
thiserror = "1"
1919
tikv-jemalloc-ctl = "0.5"
@@ -24,7 +24,7 @@ tracing = { version = "0.1" }
2424
[features]
2525
default = ["std", "jemalloc-profiling", "set-jemalloc-global"]
2626
std = ["tikv-jemalloc-ctl/use_std"]
27-
jemalloc-profiling = []
27+
jemalloc-profiling = ["dep:backtrace"]
2828
oompanic-allocator = []
2929
set-jemalloc-global = []
3030
disable_aslr = ["dep:libc"]

microchassis/src/profiling/jeprof.rs

Lines changed: 19 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@
1919
2020
use crate::profiling::mallctl;
2121
use http::{header, Method, Request, Response, StatusCode};
22-
use std::{env, fs::File, io, num::ParseIntError, process::Command};
22+
use std::env;
2323

2424
#[inline]
25-
pub fn router(sym: &SymbolTable, req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
25+
pub fn router(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
2626
match (req.method(), req.uri().path()) {
2727
(&Method::GET, "/pprof/conf") => get_pprof_conf_handler(req),
2828
(&Method::POST, "/pprof/conf") => post_pprof_conf_handler(req),
2929
(&Method::GET, "/pprof/heap") => get_pprof_heap_handler(req),
3030
(&Method::GET, "/pprof/cmdline") => get_pprof_cmdline_handler(req),
31-
(&Method::GET, "/pprof/symbol") => get_pprof_symbol_handler(sym, req),
32-
(&Method::POST, "/pprof/symbol") => post_pprof_symbol_handler(sym, req),
31+
(&Method::GET, "/pprof/symbol") => get_pprof_symbol_handler(req),
32+
(&Method::POST, "/pprof/symbol") => post_pprof_symbol_handler(req),
3333
(&Method::GET, "/pprof/stats") => get_pprof_stats_handler(req),
3434
_ => {
3535
let body = b"Bad Request\r\n";
@@ -128,27 +128,29 @@ pub fn get_pprof_cmdline_handler(_req: Request<Vec<u8>>) -> http::Result<Respons
128128

129129
/// HTTP handler for GET /pprof/symbol.
130130
#[inline]
131-
pub fn get_pprof_symbol_handler(
132-
sym: &SymbolTable,
133-
_req: Request<Vec<u8>>,
134-
) -> http::Result<Response<Vec<u8>>> {
135-
let num_symbols = sym.len();
136-
let body = format!("num_symbols: {num_symbols}\r\n");
137-
response_ok(body.into_bytes())
131+
pub fn get_pprof_symbol_handler(_req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
132+
// TODO: any quick way to check if binary is stripped?
133+
let body = b"num_symbols: 1\r\n";
134+
response_ok(body.to_vec())
138135
}
139136

140137
/// HTTP handler for POST /pprof/symbol.
141138
#[inline]
142-
pub fn post_pprof_symbol_handler(
143-
sym: &SymbolTable,
144-
req: Request<Vec<u8>>,
145-
) -> http::Result<Response<Vec<u8>>> {
139+
pub fn post_pprof_symbol_handler(req: Request<Vec<u8>>) -> http::Result<Response<Vec<u8>>> {
140+
fn lookup_symbol(addr: u64) -> Option<String> {
141+
let mut s: Option<String> = None;
142+
backtrace::resolve(addr as *mut _, |symbol| {
143+
s = symbol.name().map(|n| n.to_string());
144+
});
145+
s
146+
}
147+
146148
let body = String::from_utf8_lossy(req.body());
147149
let addrs = body
148150
.split('+')
149151
.filter_map(|addr| u64::from_str_radix(addr.trim_start_matches("0x"), 16).ok())
150-
.map(|addr| (addr, sym.lookup_symbol(addr)))
151-
.filter_map(|(addr, sym)| sym.map(|(_, sym)| (addr, sym)));
152+
.map(|addr| (addr, lookup_symbol(addr)))
153+
.filter_map(|(addr, sym)| sym.map(|sym| (addr, sym)));
152154

153155
let mut body = String::new();
154156
for (addr, sym) in addrs {
@@ -207,145 +209,3 @@ fn response_err(msg: &str) -> http::Result<Response<Vec<u8>>> {
207209
.header(header::CONTENT_LENGTH, msg.len())
208210
.body(msg.as_bytes().to_owned())
209211
}
210-
211-
#[derive(Default, Debug)]
212-
pub struct SymbolTable {
213-
sym: Vec<(u64, String)>,
214-
vstart: u64,
215-
vend: u64,
216-
fstart: u64,
217-
}
218-
219-
impl SymbolTable {
220-
#[inline]
221-
pub fn load() -> io::Result<Self> {
222-
let nm_output = run_nm()?;
223-
let (vstart, vend, fstart) = Self::load_mapping()?;
224-
let mut sym = SymbolTable { sym: Vec::default(), vstart, vend, fstart };
225-
sym.read_nm(nm_output.as_ref())
226-
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
227-
Ok(sym)
228-
}
229-
230-
fn load_mapping() -> io::Result<(u64, u64, u64)> {
231-
#[cfg(target_os = "linux")]
232-
{
233-
use std::io::Read;
234-
235-
// TODO: clean up maps parsing, store all exec mappings
236-
let exepath = env::current_exe()?;
237-
let mut f = File::open("/proc/self/maps")?;
238-
let mut buf = String::with_capacity(4096);
239-
f.read_to_string(&mut buf)?;
240-
for line in buf.lines() {
241-
let parts: Vec<_> = line.splitn(6, ' ').map(str::trim).collect();
242-
if parts.len() < 6 {
243-
continue;
244-
}
245-
if parts[5] == exepath.to_string_lossy() && parts[1] == "r-xp" {
246-
let addr_range: Vec<_> = parts[0]
247-
.splitn(2, '-')
248-
.filter_map(|n| u64::from_str_radix(n, 16).ok())
249-
.collect();
250-
if addr_range.len() != 2 {
251-
continue;
252-
}
253-
let file_offset = u64::from_str_radix(parts[2], 16)
254-
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
255-
256-
return Ok((addr_range[0], addr_range[1], file_offset));
257-
}
258-
}
259-
}
260-
261-
Ok((u64::MAX, u64::MAX, 0))
262-
}
263-
264-
fn read_nm(&mut self, output: &[u8]) -> Result<(), ParseIntError> {
265-
use std::io::prelude::*;
266-
267-
let b = io::Cursor::new(output);
268-
for line in b.lines() {
269-
let line = line.expect("no I/O, no panic");
270-
let parts: Vec<_> = line.split_ascii_whitespace().collect();
271-
if parts.len() < 3 || parts[0] == "U" {
272-
continue;
273-
}
274-
if parts[1] != "t" && parts[1] != "T" {
275-
continue;
276-
}
277-
278-
let address = u64::from_str_radix(parts[0].trim_start_matches("0x"), 16)?;
279-
let symbol: String = parts[2..].join(" ");
280-
let symbol = rustc_demangle::demangle(symbol.as_str());
281-
282-
self.sym.push((address, symbol.to_string()));
283-
}
284-
285-
self.sym.sort();
286-
287-
Ok(())
288-
}
289-
290-
#[must_use]
291-
#[inline]
292-
pub fn len(&self) -> usize {
293-
self.sym.len()
294-
}
295-
296-
#[must_use]
297-
#[inline]
298-
pub fn is_empty(&self) -> bool {
299-
self.sym.is_empty()
300-
}
301-
302-
#[must_use]
303-
#[inline]
304-
pub fn lookup_symbol(&self, addr: u64) -> Option<&(u64, String)> {
305-
let lookup_addr = if addr >= self.vstart && addr < self.vend {
306-
addr - self.vstart + self.fstart
307-
} else {
308-
addr
309-
};
310-
311-
match self.sym.binary_search_by_key(&lookup_addr, |(saddr, _)| *saddr) {
312-
Ok(index) => self.sym.get(index),
313-
Err(index) => {
314-
if index == 0 {
315-
None
316-
} else {
317-
self.sym.get(index - 1)
318-
}
319-
}
320-
}
321-
}
322-
}
323-
324-
fn run_nm() -> io::Result<Vec<u8>> {
325-
let exepath = env::current_exe()?;
326-
let output =
327-
Command::new("nm").args(["--numeric-sort", "--no-demangle"]).arg(exepath).output()?;
328-
Ok(output.stdout)
329-
}
330-
331-
#[cfg(test)]
332-
mod tests {
333-
use super::*;
334-
335-
#[test]
336-
fn test_symtab_lookup_symbol() {
337-
let symtab = SymbolTable {
338-
sym: vec![(123, "Abc".to_string()), (456, "Def".to_string()), (789, "Xyz".to_string())],
339-
vstart: 0,
340-
vend: u64::MAX,
341-
fstart: 0,
342-
};
343-
344-
assert_eq!(None, symtab.lookup_symbol(100));
345-
assert_eq!(Some(&(123, "Abc".to_string())), symtab.lookup_symbol(123));
346-
assert_eq!(Some(&(123, "Abc".to_string())), symtab.lookup_symbol(200));
347-
assert_eq!(Some(&(123, "Abc".to_string())), symtab.lookup_symbol(455));
348-
assert_eq!(Some(&(456, "Def".to_string())), symtab.lookup_symbol(456));
349-
assert_eq!(Some(&(789, "Xyz".to_string())), symtab.lookup_symbol(800));
350-
}
351-
}

0 commit comments

Comments
 (0)