Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add built-in function for formatting MAC addresses #1647

Merged
merged 1 commit into from
Jan 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ and this project adheres to
- [#1619](https://github.com/iovisor/bpftrace/issues/1619)
- Array improvements (support assignment to variables and usage as a map key)
- [#1656](https://github.com/iovisor/bpftrace/pull/1656)
- Add builtin function: `macaddr`
- [#1647](https://github.com/iovisor/bpftrace/pull/1647)

#### Changed
- Warn if using `print` on `stats` maps with top and div arguments
Expand Down
15 changes: 15 additions & 0 deletions docs/reference_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ discussion to other files in /docs, the /tools/\*\_examples.txt files, or blog p
- [25. `path()`: Return full path](#25-path-return-full-path)
- [26. `uptr()`: Annotate userspace pointer](#26-uptr-annotate-userspace-pointer)
- [27. `kptr()`: Annotate kernelspace pointer](#27-kptr-annotate-kernelspace-pointer)
- [28. `macaddr()`: Convert MAC address data to text](#28-macaddr-convert-mac-address-data-to-text)
- [Map Functions](#map-functions)
- [1. Builtins](#1-builtins-2)
- [2. `count()`: Count](#2-count-count)
Expand Down Expand Up @@ -2009,6 +2010,7 @@ Tracing block I/O sizes > 0 bytes
- `path(struct path *path)` - Return full path
- `uptr(void *p)` - Annotate as userspace pointer
- `kptr(void *p)` - Annotate as kernelspace pointer
- `macaddr(char[6] addr)` - Convert MAC address data

Some of these are asynchronous: the kernel queues the event, but some time later (milliseconds) it is
processed in user-space. The asynchronous actions are: `printf()`, `time()`, and `join()`. Both `ksym()`
Expand Down Expand Up @@ -2862,6 +2864,19 @@ Annotate `p` as a pointer belonging to kernel address space.
Just like `uptr`, you'll generally only need this if bpftrace has inferred the
pointer address space incorrectly.

## 28. `macaddr()`: Convert MAC address data to text

Syntax: `macaddr(char[6] addr)`

This returns the canonical string representation of a MAC address.

Example:

```
# bpftrace -e 'kprobe:arp_create { printf("SRC %s, DST %s\n", macaddr(sarg0), macaddr(sarg1)); }'
SRC 18:C0:4D:08:2E:BB, DST 74:83:C2:7F:8C:FF
^C
```

# Map Functions

Expand Down
3 changes: 2 additions & 1 deletion src/ast/codegen_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ inline bool needMemcpy(const SizedType &stype)
inline bool shouldBeOnStackAlready(const SizedType &type)
{
return type.IsStringTy() || type.IsBufferTy() || type.IsInetTy() ||
type.IsUsymTy() || type.IsTupleTy() || type.IsTimestampTy();
type.IsUsymTy() || type.IsTupleTy() || type.IsTimestampTy() ||
type.IsMacAddressTy();
}
inline AddrSpace find_addrspace_stack(const SizedType &ty)
{
Expand Down
17 changes: 17 additions & 0 deletions src/ast/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,23 @@ void CodegenLLVM::visit(Call &call)
auto arg = call.vargs->at(0);
auto scoped_del = accept(arg);
}
else if (call.func == "macaddr")
{
// MAC addresses are presented as char[6]
AllocaInst *buf = b_.CreateAllocaBPFInit(call.type, "macaddr");
auto macaddr = call.vargs->front();
auto scoped_del = accept(macaddr);

b_.CreateProbeRead(ctx_,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC arrays can end up on stack already so this would probe read from the stack in that case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should I check for that case? I see that shouldBeOnStackAlready doesn't include arrays, so it wouldn't be enough to branch on that. But I'm thinking that we need to branch on something and then store (instead of probe read) if the arg is already on the stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it would have to load/memcpy instead of probe reading.

I dont know the best way to check, arrays need some love. I'd be ok with this probe reading for now too, can always fix it in the future.

static_cast<AllocaInst *>(buf),
macaddr->type.GetSize(),
expr_,
macaddr->type.GetAS(),
call.loc);

expr_ = buf;
expr_deleter_ = [this, buf]() { b_.CreateLifetimeEnd(buf); };
}
else
{
LOG(FATAL) << "missing codegen for function \"" << call.func << "\"";
Expand Down
20 changes: 20 additions & 0 deletions src/ast/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,26 @@ void SemanticAnalyser::visit(Call &call)
call.type = call.vargs->front()->type;
call.type.SetAS(as);
}
else if (call.func == "macaddr")
{
if (!check_nargs(call, 1))
return;

auto &arg = call.vargs->at(0);

if (!arg->type.IsIntTy() && !arg->type.IsArrayTy() &&
!arg->type.IsByteArray() && !arg->type.IsPtrTy())
LOG(ERROR, call.loc, err_)
<< call.func << "() only supports array or pointer arguments"
<< " (" << arg->type.type << " provided)";

auto type = arg->type;
if ((type.IsArrayTy() || type.IsByteArray()) && type.GetSize() != 6)
LOG(ERROR, call.loc, err_)
<< call.func << "() argument must be 6 bytes in size";

call.type = CreateMacAddress();
}
else
{
LOG(ERROR, call.loc, err_) << "Unknown function: '" << call.func << "'";
Expand Down
23 changes: 23 additions & 0 deletions src/bpftrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,11 @@ std::vector<std::unique_ptr<IPrintable>> BPFtrace::get_arg_values(const std::vec
arg_values.push_back(std::make_unique<PrintableInt>(
*reinterpret_cast<uint64_t *>(arg_data + arg.offset)));
break;
case Type::mac_address:
arg_values.push_back(
std::make_unique<PrintableString>(resolve_mac_address(
reinterpret_cast<uint8_t *>(arg_data + arg.offset))));
break;
// fall through
default:
LOG(FATAL) << "invalid argument type";
Expand Down Expand Up @@ -1452,6 +1457,8 @@ std::string BPFtrace::map_value_to_str(const SizedType &stype,
reinterpret_cast<AsyncEvent::Strftime *>(value.data())->strftime_id,
reinterpret_cast<AsyncEvent::Strftime *>(value.data())
->nsecs_since_boot);
else if (stype.IsMacAddressTy())
return resolve_mac_address(value.data());
else
return std::to_string(read_data<int64_t>(value.data()) / div);
}
Expand Down Expand Up @@ -1976,6 +1983,22 @@ int BPFtrace::resolve_uname(const std::string &name,
#endif
}

std::string BPFtrace::resolve_mac_address(const uint8_t *mac_addr) const
{
const size_t SIZE = 18;
char addr[SIZE];
snprintf(addr,
SIZE,
"%02X:%02X:%02X:%02X:%02X:%02X",
mac_addr[0],
mac_addr[1],
mac_addr[2],
mac_addr[3],
mac_addr[4],
mac_addr[5]);
return std::string(addr);
}

#ifdef HAVE_BCC_ELF_FOREACH_SYM
static int add_symbol(const char *symname, uint64_t /*start*/, uint64_t /*size*/, void *payload) {
auto syms = static_cast<std::set<std::string> *>(payload);
Expand Down
1 change: 1 addition & 0 deletions src/bpftrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class BPFtrace
virtual int resolve_uname(const std::string &name,
struct symbol *sym,
const std::string &path) const;
std::string resolve_mac_address(const uint8_t *mac_addr) const;
std::string map_value_to_str(const SizedType &stype,
std::vector<uint8_t> value,
bool is_per_cpu,
Expand Down
2 changes: 1 addition & 1 deletion src/lexer.l
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vspace [\n\r]
space {hspace}|{vspace}
path :(\\.|[_\-\./a-zA-Z0-9#\*])*:
builtin arg[0-9]|args|cgroup|comm|cpid|cpu|ctx|curtask|elapsed|func|gid|nsecs|pid|probe|rand|retval|sarg[0-9]|tid|uid|username
call avg|buf|cat|cgroupid|clear|count|delete|exit|hist|join|kaddr|kptr|ksym|lhist|max|min|ntop|override|print|printf|reg|signal|sizeof|stats|str|strftime|strncmp|sum|system|time|uaddr|uptr|usym|zero|path
call avg|buf|cat|cgroupid|clear|count|delete|exit|hist|join|kaddr|kptr|ksym|lhist|macaddr|max|min|ntop|override|print|printf|reg|signal|sizeof|stats|str|strftime|strncmp|sum|system|time|uaddr|uptr|usym|zero|path

/* Don't add to this! Use builtin OR call not both */
call_and_builtin kstack|ustack
Expand Down
5 changes: 5 additions & 0 deletions src/mapkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ std::string MapKey::argument_value(BPFtrace &bpftrace,
i * arg.GetElementTy()->GetSize()));
return "[" + str_join(elems, ",") + "]";
}
case Type::mac_address:
{
auto p = static_cast<const uint8_t *>(data);
return bpftrace.resolve_mac_address(p);
}
default:
LOG(ERROR) << "invalid mapkey argument type";
}
Expand Down
3 changes: 2 additions & 1 deletion src/printf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ std::string verify_format_string(const std::string &fmt, std::vector<Field> args
if (arg_type == Type::ksym || arg_type == Type::usym ||
arg_type == Type::probe || arg_type == Type::username ||
arg_type == Type::kstack || arg_type == Type::ustack ||
arg_type == Type::inet || arg_type == Type::timestamp)
arg_type == Type::inet || arg_type == Type::timestamp ||
arg_type == Type::mac_address)
arg_type = Type::string; // Symbols should be printed as strings
if (arg_type == Type::pointer)
arg_type = Type::integer; // Casts (pointers) can be printed as integers
Expand Down
13 changes: 11 additions & 2 deletions src/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ bool SizedType::operator==(const SizedType &t) const
bool SizedType::IsByteArray() const
{
return type == Type::string || type == Type::usym || type == Type::inet ||
type == Type::buffer || type == Type::timestamp;
type == Type::buffer || type == Type::timestamp ||
type == Type::mac_address;
}

bool SizedType::IsAggregate() const
Expand Down Expand Up @@ -174,7 +175,8 @@ std::string typestr(Type t)
case Type::buffer: return "buffer"; break;
case Type::tuple: return "tuple"; break;
case Type::timestamp:return "timestamp";break;
// clang-format on
case Type::mac_address: return "mac_address"; break;
// clang-format on
}

return {}; // unreached
Expand Down Expand Up @@ -446,6 +448,13 @@ SizedType CreateTuple(const std::vector<SizedType> &fields)
return s;
}

SizedType CreateMacAddress()
{
auto st = SizedType(Type::mac_address, 6);
st.is_internal = true;
return st;
}

bool SizedType::IsSigned(void) const
{
return is_signed_;
Expand Down
8 changes: 7 additions & 1 deletion src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ enum class Type
array,
buffer,
tuple,
timestamp
timestamp,
mac_address
// clang-format on
};

Expand Down Expand Up @@ -326,6 +327,10 @@ class SizedType
{
return type == Type::timestamp;
};
bool IsMacAddressTy(void) const
{
return type == Type::mac_address;
};

friend std::ostream &operator<<(std::ostream &, const SizedType &);
friend std::ostream &operator<<(std::ostream &, Type);
Expand Down Expand Up @@ -386,6 +391,7 @@ SizedType CreateKSym();
SizedType CreateJoin(size_t argnum, size_t argsize);
SizedType CreateBuffer(size_t size);
SizedType CreateTimestamp();
SizedType CreateMacAddress();

std::ostream &operator<<(std::ostream &os, const SizedType &type);

Expand Down
17 changes: 17 additions & 0 deletions tests/codegen/call_macaddr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "common.h"

namespace bpftrace {
namespace test {
namespace codegen {

TEST(codegen, call_macaddr)
{
test("struct mac { unsigned char addr[6] } kprobe:f { @x = macaddr(((struct "
"mac*)0)->addr); }",

NAME);
}

} // namespace codegen
} // namespace test
} // namespace bpftrace
42 changes: 42 additions & 0 deletions tests/codegen/llvm/call_macaddr.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
; ModuleID = 'bpftrace'
source_filename = "bpftrace"
target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128"
target triple = "bpf-pc-linux"

; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0

define i64 @"kprobe:f"(i8*) section "s_kprobe:f_1" {
entry:
%"@x_key" = alloca i64
%macaddr = alloca [6 x i8]
%1 = bitcast [6 x i8]* %macaddr to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %1)
%2 = bitcast [6 x i8]* %macaddr to i8*
call void @llvm.memset.p0i8.i64(i8* align 1 %2, i8 0, i64 6, i1 false)
%3 = bitcast [6 x i8]* %macaddr to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %3)
%probe_read_kernel = call i64 inttoptr (i64 113 to i64 ([6 x i8]*, i32, i64)*)([6 x i8]* %macaddr, i32 6, i64 0)
%4 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %4)
store i64 0, i64* %"@x_key"
%pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [6 x i8]*, i64)*)(i64 %pseudo, i64* %"@x_key", [6 x i8]* %macaddr, i64 0)
%5 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %5)
%6 = bitcast [6 x i8]* %macaddr to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %6)
ret i64 0
}

; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1

; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1

; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1

attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
15 changes: 15 additions & 0 deletions tests/runtime/call
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,18 @@ EXPECT OK
REQUIRES_FEATURE dpath
TIMEOUT 5
AFTER ./testprogs/syscall read

NAME macaddr
RUN bpftrace -e 'struct MyStruct { const char* ignore; char mac[6]; }; u:./testprogs/complex_struct:func { $s = ((struct MyStruct *)arg0); printf("P: %s\n", macaddr($s->mac)); exit(); }' -c ./testprogs/complex_struct
EXPECT P: 05:04:03:02:01:02
TIMEOUT 5

NAME macaddr as map key
RUN bpftrace -e 'struct MyStruct { const char* ignore; char mac[6]; }; u:./testprogs/complex_struct:func { $s = ((struct MyStruct *)arg0); @[macaddr($s->mac)] = 1; exit(); }' -c ./testprogs/complex_struct
EXPECT @\[05:04:03:02:01:02\]: 1
TIMEOUT 5

NAME macaddr as map value
RUN bpftrace -e 'struct MyStruct { const char* ignore; char mac[6]; }; u:./testprogs/complex_struct:func { $s = ((struct MyStruct *)arg0); @[1] = macaddr($s->mac); exit(); }' -c ./testprogs/complex_struct
EXPECT \[1\]: 05:04:03:02:01:02
TIMEOUT 5
22 changes: 22 additions & 0 deletions tests/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ TEST(semantic_analyser, builtin_functions)
test("kprobe:f { cat(\"/proc/uptime\") }", 0);
test("uprobe:/bin/bash:main { uaddr(\"glob_asciirange\") }", 0);
test("kprobe:f { cgroupid(\"/sys/fs/cgroup/unified/mycg\"); }", 0);
test("kprobe:f { macaddr(0xffff) }", 0);
}

TEST(semantic_analyser, undefined_map)
Expand Down Expand Up @@ -806,6 +807,27 @@ TEST(semantic_analyser, call_stack)
test("kprobe:f { @x = 3; ustack(perf, @x) }", 1);
}

TEST(semantic_analyser, call_macaddr)
{
std::string structs =
"struct mac { char addr[6]; }; struct invalid { char addr[7]; }; ";

test("kprobe:f { macaddr(arg0); }", 0);

test(structs + "kprobe:f { macaddr((struct mac*)arg0); }", 0);

test(structs + "kprobe:f { @x[macaddr((struct mac*)arg0)] = 1; }", 0);
acj marked this conversation as resolved.
Show resolved Hide resolved
test(structs + "kprobe:f { @x = macaddr((struct mac*)arg0); }", 0);

test(structs + "kprobe:f { printf(\"%s\", macaddr((struct mac*)arg0)); }", 0);

test(structs + "kprobe:f { macaddr(((struct invalid*)arg0)->addr); }", 1);
test(structs + "kprobe:f { macaddr(*(struct mac*)arg0); }", 1);

test("kprobe:f { macaddr(); }", 1);
test("kprobe:f { macaddr(\"hello\"); }", 1);
}

TEST(semantic_analyser, map_reassignment)
{
test("kprobe:f { @x = 1; @x = 2; }", 0);
Expand Down