forked from iovisor/bcc
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
3,052 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
Lua Tools for BCC | ||
----------------- | ||
|
||
This directory contains Lua tooling for [BCC](https://github.com/iovisor/bcc) | ||
(the BPF Compiler Collection). | ||
|
||
BCC is a toolkit for creating userspace and kernel tracing programs. By | ||
default, it comes with a library `libbcc`, some example tooling and a Python | ||
frontend for the library. | ||
|
||
Here we present an alternate frontend for `libbcc` implemented in LuaJIT. This | ||
lets you write the userspace part of your tracer in Lua instead of Python. | ||
|
||
Since LuaJIT is a JIT compiled language, tracers implemented in `bcc-lua` | ||
exhibit significantly reduced overhead compared to their Python equivalents. | ||
This is particularly noticeable in tracers that actively use the table APIs to | ||
get information from the kernel. | ||
|
||
If your tracer makes extensive use of `BPF_MAP_TYPE_PERF_EVENT_ARRAY` or | ||
`BPF_MAP_TYPE_HASH`, you may find the performance characteristics of this | ||
implementation very appealing, as LuaJIT can compile to native code a lot of | ||
the callchain to process the events, and this wrapper has been designed to | ||
benefit from such JIT compilation. | ||
|
||
## Quickstart Guide | ||
|
||
The following instructions assume Ubuntu 14.04 LTS. | ||
|
||
1. Install a **very new kernel**. It has to be new and shiny for this to work. 4.3+ | ||
|
||
``` | ||
VER=4.4.2-040402 | ||
PREFIX=http:https://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4.2-wily/ | ||
REL=201602171633 | ||
wget ${PREFIX}/linux-headers-${VER}-generic_${VER}.${REL}_amd64.deb | ||
wget ${PREFIX}/linux-headers-${VER}_${VER}.${REL}_all.deb | ||
wget ${PREFIX}/linux-image-${VER}-generic_${VER}.${REL}_amd64.deb | ||
sudo dpkg -i linux-*${VER}.${REL}*.deb | ||
``` | ||
|
||
2. Install the `libbcc` binary packages and `luajit` | ||
|
||
``` | ||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D4284CDD | ||
echo "deb http:https://52.8.15.63/apt trusty main" | sudo tee /etc/apt/sources.list.d/iovisor.list | ||
sudo apt-get update | ||
sudo apt-get install libbcc luajit | ||
``` | ||
|
||
3. Test one of the examples to ensure `libbcc` is properly installed | ||
|
||
``` | ||
sudo ./bcc-probe examples/lua/task_switch.lua | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env luajit | ||
--[[ | ||
Copyright 2016 GitHub, Inc | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http:https://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
]] | ||
package.path = "./?/init.lua;"..package.path | ||
|
||
local BCC = require("bcc") | ||
local BPF = BCC.BPF | ||
|
||
BPF.script_root(arg[1]) | ||
|
||
local utils = { | ||
argparse = require("bcc.vendor.argparse"), | ||
posix = require("bcc.vendor.posix"), | ||
sym = BCC.sym | ||
} | ||
|
||
local tracefile = table.remove(arg, 1) | ||
local command = require(tracefile:gsub("%.lua", "")) | ||
local res, err = pcall(command, BPF, utils) | ||
|
||
if not res then | ||
io.stderr:write("[ERROR] "..err.."\n") | ||
end | ||
|
||
BPF.cleanup_probes() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
--[[ | ||
Copyright 2016 GitHub, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http:https://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
]] | ||
local ffi = require("ffi") | ||
local libbcc = require("bcc.libbcc") | ||
|
||
local TracerPipe = require("bcc.tracerpipe") | ||
local Table = require("bcc.table") | ||
local LD = require("bcc.ld") | ||
|
||
local Bpf = class("BPF") | ||
|
||
Bpf.static.open_kprobes = {} | ||
Bpf.static.open_uprobes = {} | ||
Bpf.static.process_symbols = {} | ||
Bpf.static.KPROBE_LIMIT = 1000 | ||
Bpf.static.tracer_pipe = nil | ||
|
||
function Bpf.static.check_probe_quota(n) | ||
local cur = table.count(Bpf.static.open_kprobes) + table.count(Bpf.static.open_uprobes) | ||
assert(cur + n <= Bpf.static.KPROBE_LIMIT, "number of open probes would exceed quota") | ||
end | ||
|
||
function Bpf.static.cleanup_probes() | ||
local function detach_all(probe_type, all_probes) | ||
for key, probe in pairs(all_probes) do | ||
libbcc.perf_reader_free(probe) | ||
if type(key) == "string" then | ||
local desc = string.format("-:%s/%s", probe_type, key) | ||
-- TODO: why so noisy? | ||
--libbcc.bpf_detach_kprobe(desc) | ||
end | ||
all_probes[key] = nil | ||
end | ||
end | ||
|
||
detach_all("kprobes", Bpf.static.open_kprobes) | ||
detach_all("uprobes", Bpf.static.open_uprobes) | ||
if Bpf.static.tracer_pipe ~= nil then | ||
Bpf.static.tracer_pipe:close() | ||
end | ||
end | ||
|
||
function Bpf.static.num_open_kprobes() | ||
return table.count(Bpf.static.open_kprobes) | ||
end | ||
|
||
function Bpf.static.usymaddr(pid, addr, refresh) | ||
local proc_sym = Bpf.static.process_symbols[pid] | ||
|
||
if proc_sym == nil then | ||
proc_sym = ProcSymbols(pid) | ||
Bpf.static.process_symbols[pid] = proc_sym | ||
elseif refresh then | ||
proc_sym.refresh() | ||
end | ||
|
||
return proc_sym.decode_addr(addr) | ||
end | ||
|
||
Bpf.static.SCRIPT_ROOT = "./" | ||
function Bpf.static.script_root(root) | ||
local dir, file = root:match'(.*/)(.*)' | ||
Bpf.static.SCRIPT_ROOT = dir or "./" | ||
return Bpf | ||
end | ||
|
||
local function _find_file(script_root, filename) | ||
if filename == nil then | ||
return nil | ||
end | ||
|
||
if os.exists(filename) then | ||
return filename | ||
end | ||
|
||
if not filename:starts("/") then | ||
filename = script_root .. filename | ||
if os.exists(filename) then | ||
return filename | ||
end | ||
end | ||
|
||
assert(nil, "failed to find file "..filename.." (root="..script_root..")") | ||
end | ||
|
||
function Bpf:initialize(args) | ||
self.do_debug = args.debug or false | ||
self.funcs = {} | ||
self.tables = {} | ||
|
||
if args.text then | ||
log.info("\n%s\n", args.text) | ||
self.module = libbcc.bpf_module_create_c_from_string(args.text, args.llvm_debug or 0, nil, 0) | ||
elseif args.src_file then | ||
local src = _find_file(Bpf.SCRIPT_ROOT, args.src_file) | ||
|
||
if src:ends(".b") then | ||
local hdr = _find_file(Bpf.SCRIPT_ROOT, args.hdr_file) | ||
self.module = libbcc.bpf_module_create_b(src, hdr, args.llvm_debug or 0) | ||
else | ||
self.module = libbcc.bpf_module_create_c(src, args.llvm_debug or 0, nil, 0) | ||
end | ||
end | ||
|
||
assert(self.module ~= nil, "failed to compile BPF module") | ||
end | ||
|
||
function Bpf:load_func(fn_name, prog_type) | ||
if self.funcs[fn_name] ~= nil then | ||
return self.funcs[fn_name] | ||
end | ||
|
||
assert(libbcc.bpf_function_start(self.module, fn_name), "unknown program: "..fn_name) | ||
|
||
local fd = libbcc.bpf_prog_load(prog_type, | ||
libbcc.bpf_function_start(self.module, fn_name), | ||
libbcc.bpf_function_size(self.module, fn_name), | ||
libbcc.bpf_module_license(self.module), | ||
libbcc.bpf_module_kern_version(self.module), nil, 0) | ||
|
||
assert(fd >= 0, "failed to load BPF program "..fn_name) | ||
log.info("loaded %s (%d)", fn_name, fd) | ||
|
||
local fn = {bpf=self, name=fn_name, fd=fd} | ||
self.funcs[fn_name] = fn | ||
return fn | ||
end | ||
|
||
function Bpf:attach_uprobe(args) | ||
Bpf.check_probe_quota(1) | ||
|
||
local path, addr = LD.check_path_symbol(args.name, args.sym, args.addr) | ||
local fn = self:load_func(args.fn_name, 'BPF_PROG_TYPE_KPROBE') | ||
local ptype = args.retprobe and "r" or "p" | ||
local ev_name = string.format("%s_%s_0x%x", ptype, path:gsub("[^%a%d]", "_"), addr) | ||
local desc = string.format("%s:uprobes/%s %s:0x%x", ptype, ev_name, path, addr) | ||
|
||
log.info(desc) | ||
|
||
local res = libbcc.bpf_attach_uprobe(fn.fd, ev_name, desc, | ||
args.pid or -1, | ||
args.cpu or 0, | ||
args.group_fd or -1, nil, nil) -- TODO; reader callback | ||
|
||
assert(res ~= nil, "failed to attach BPF to uprobe") | ||
self:probe_store("uprobe", ev_name, res) | ||
return self | ||
end | ||
|
||
function Bpf:attach_kprobe(args) | ||
-- TODO: allow the caller to glob multiple functions together | ||
Bpf.check_probe_quota(1) | ||
|
||
local fn = self:load_func(args.fn_name, 'BPF_PROG_TYPE_KPROBE') | ||
local event = args.event or "" | ||
local ptype = args.retprobe and "r" or "p" | ||
local ev_name = string.format("%s_%s", ptype, event:gsub("[%+%.]", "_")) | ||
local desc = string.format("%s:kprobes/%s %s", ptype, ev_name, event) | ||
|
||
log.info(desc) | ||
|
||
local res = libbcc.bpf_attach_kprobe(fn.fd, ev_name, desc, | ||
args.pid or -1, | ||
args.cpu or 0, | ||
args.group_fd or -1, nil, nil) -- TODO; reader callback | ||
|
||
assert(res ~= nil, "failed to attach BPF to kprobe") | ||
self:probe_store("kprobe", ev_name, res) | ||
return self | ||
end | ||
|
||
function Bpf:pipe() | ||
if Bpf.tracer_pipe == nil then | ||
Bpf.tracer_pipe = TracerPipe:new() | ||
end | ||
return Bpf.tracer_pipe | ||
end | ||
|
||
function Bpf:get_table(name, key_type, leaf_type) | ||
if self.tables[name] == nil then | ||
self.tables[name] = Table(self, name, key_type, leaf_type) | ||
end | ||
return self.tables[name] | ||
end | ||
|
||
function Bpf:probe_store(t, id, reader) | ||
if t == "kprobe" then | ||
Bpf.open_kprobes[id] = reader | ||
elseif t == "uprobe" then | ||
Bpf.open_uprobes[id] = reader | ||
else | ||
error("unknown probe type '%s'" % t) | ||
end | ||
|
||
log.info("%s -> %s", id, reader) | ||
end | ||
|
||
function Bpf:probe_lookup(t, id) | ||
if t == "kprobe" then | ||
return Bpf.open_kprobes[id] | ||
elseif t == "uprobe" then | ||
return Bpf.open_uprobes[id] | ||
else | ||
return nil | ||
end | ||
end | ||
|
||
function Bpf:_kprobe_array() | ||
local kprobe_count = table.count(Bpf.open_kprobes) | ||
local readers = ffi.new("struct perf_reader*[?]", kprobe_count) | ||
local n = 0 | ||
|
||
for _, r in pairs(Bpf.open_kprobes) do | ||
readers[n] = r | ||
n = n + 1 | ||
end | ||
|
||
assert(n == kprobe_count, "wtf") | ||
return readers, n | ||
end | ||
|
||
function Bpf:kprobe_poll_loop() | ||
local probes, probe_count = self:_kprobe_array() | ||
return pcall(function() | ||
while true do | ||
libbcc.perf_reader_poll(probe_count, probes, -1) | ||
end | ||
end) | ||
end | ||
|
||
function Bpf:kprobe_poll(timeout) | ||
local probes, probe_count = self:_kprobe_array() | ||
libbcc.perf_reader_poll(probe_count, probes, timeout or -1) | ||
end | ||
|
||
return Bpf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--[[ | ||
Copyright 2016 GitHub, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http:https://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
]] | ||
require("bcc.vendor.strict") | ||
require("bcc.vendor.helpers") | ||
class = require("bcc.vendor.middleclass") | ||
|
||
return { | ||
BPF = require("bcc.bpf"), | ||
sym = require("bcc.sym"), | ||
} |
Oops, something went wrong.