Skip to content

Commit

Permalink
Enable printf, cat, system to have more args
Browse files Browse the repository at this point in the history
This enables printf, cat and system to have more than 7 arguments. It can be
more user-friendly to allow any reasonable number of arguments for these builtin
calls which use formatted strings. Resolves #1336.
  • Loading branch information
suyuee committed Jul 8, 2020
1 parent 24205c9 commit f2cdf87
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 81 deletions.
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
- [#1377](https://github.com/iovisor/bpftrace/pull/1377)
- Add support for non-map print()
- [#1381](https://github.com/iovisor/bpftrace/pull/1381)
- Enable `printf`, `cat` and `system` to have more than 7 arguments
- [#1404](https://github.com/iovisor/bpftrace/pull/1404)
- Enable the `ternary` operator to evaluate builtin calls
- [#1405](https://github.com/iovisor/bpftrace/pull/1405)

Expand Down
2 changes: 1 addition & 1 deletion src/ast/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ void SemanticAnalyser::visit(Call &call)
else if (call.func == "printf" || call.func == "system" || call.func == "cat")
{
check_assignment(call, false, false, false);
if (check_varargs(call, 1, 7))
if (check_varargs(call, 1, 128))
{
check_arg(call, Type::string, 0, true);
if (is_final_pass())
Expand Down
141 changes: 63 additions & 78 deletions src/bpftrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,53 +48,69 @@ namespace bpftrace {
DebugLevel bt_debug = DebugLevel::kNone;
bool bt_verbose = false;
volatile sig_atomic_t BPFtrace::exitsig_recv = false;
const int FMT_BUF_SZ = 512;

int format(char * s, size_t n, const char * fmt, std::vector<std::unique_ptr<IPrintable>> &args) {
std::string format(std::string fmt,
std::vector<std::unique_ptr<IPrintable>> &args)
{
std::string retstr;
auto buffer = std::vector<char>(FMT_BUF_SZ);
auto check_snprintf_ret = [](int r) {
if (r < 0)
{
std::cerr << "format() error occurred: " << std::strerror(errno)
<< std::endl;
abort();
}
};
// Args have been made safe for printing by now, so replace nonstandard format
// specifiers with %s
std::string str = std::string(fmt);
size_t start_pos = 0;
while ((start_pos = str.find("%r", start_pos)) != std::string::npos)
while ((start_pos = fmt.find("%r", start_pos)) != std::string::npos)
{
str.replace(start_pos, 2, "%s");
fmt.replace(start_pos, 2, "%s");
start_pos += 2;
}
fmt = str.c_str();

int ret = -1;
switch(args.size()) {
case 0:
ret = snprintf(s, n, "%s", fmt);
break;
case 1:
ret = snprintf(s, n, fmt, args.at(0)->value());
break;
case 2:
ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value());
break;
case 3:
ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value());
break;
case 4:
ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(), args.at(3)->value());
break;
case 5:
ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(),
args.at(3)->value(), args.at(4)->value());
break;
case 6:
ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(),
args.at(3)->value(), args.at(4)->value(), args.at(5)->value());
break;
default:
std::cerr << "format() can only take up to 7 arguments (" << args.size() << ") provided" << std::endl;
abort();
}
if (ret < 0 && errno != 0) {
std::cerr << "format() error occurred: " << std::strerror(errno) << std::endl;
abort();
}
return ret;
auto tokens_begin = std::sregex_iterator(fmt.begin(),
fmt.end(),
format_specifier_re);
auto tokens_end = std::sregex_iterator();

// replace format string tokens with args one by one
int literal_text_pos = 0; // starting pos of literal text (text that is not
// format specifier)
int i = 0; // args index
while (tokens_begin != tokens_end)
{
// take out the literal text
retstr += fmt.substr(literal_text_pos,
tokens_begin->position() - literal_text_pos);
// replace current specifier with an arg
int r = snprintf(buffer.data(),
buffer.capacity(),
tokens_begin->str().c_str(),
args.at(i)->value());
check_snprintf_ret(r);
if (static_cast<size_t>(r) >= buffer.capacity())
{
// the buffer is not big enough to hold the string, resize it
buffer.resize(r + 1);
r = snprintf(buffer.data(),
buffer.capacity(),
tokens_begin->str().c_str(),
args.at(i)->value());
check_snprintf_ret(r);
}
retstr += std::string(buffer.data());
// move to the next literal text
literal_text_pos = tokens_begin->position() + tokens_begin->length();
++tokens_begin;
++i;
}
// append whatever is left
retstr += fmt.substr(literal_text_pos);
return retstr;
}

BPFtrace::~BPFtrace()
Expand Down Expand Up @@ -573,66 +589,35 @@ void perf_event_printer(void *cb_cookie, void *data, int size __attribute__((unu
}

auto id = printf_id - asyncactionint(AsyncAction::syscall);
auto fmt = std::get<0>(bpftrace->system_args_[id]).c_str();
auto fmt = std::get<0>(bpftrace->system_args_[id]);
auto args = std::get<1>(bpftrace->system_args_[id]);
auto arg_values = bpftrace->get_arg_values(args, arg_data);

const int BUFSIZE = 512;
char buffer[BUFSIZE];
int sz = format(buffer, BUFSIZE, fmt, arg_values);
// Return value is required size EXCLUDING null byte
if (sz >= BUFSIZE)
{
std::cerr << "syscall() command to long (" << sz << " bytes): ";
std::cerr << buffer << std::endl;
return;
}
bpftrace->out_->message(MessageType::syscall, exec_system(buffer), false);
bpftrace->out_->message(MessageType::syscall,
exec_system(format(fmt, arg_values).c_str()),
false);
return;
}
else if ( printf_id >= asyncactionint(AsyncAction::cat))
{
auto id = printf_id - asyncactionint(AsyncAction::cat);
auto fmt = std::get<0>(bpftrace->cat_args_[id]).c_str();
auto fmt = std::get<0>(bpftrace->cat_args_[id]);
auto args = std::get<1>(bpftrace->cat_args_[id]);
auto arg_values = bpftrace->get_arg_values(args, arg_data);

const int BUFSIZE = 512;
char buffer[BUFSIZE];
int sz = format(buffer, BUFSIZE, fmt, arg_values);
// Return value is required size EXCLUDING null byte
if (sz >= BUFSIZE)
{
std::cerr << "cat() command to long (" << sz << " bytes): ";
std::cerr << buffer << std::endl;
return;
}
std::stringstream buf;
cat_file(buffer, bpftrace->cat_bytes_max_, buf);
cat_file(format(fmt, arg_values).c_str(), bpftrace->cat_bytes_max_, buf);
bpftrace->out_->message(MessageType::cat, buf.str(), false);

return;
}

// printf
auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str();
auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]);
auto args = std::get<1>(bpftrace->printf_args_[printf_id]);
auto arg_values = bpftrace->get_arg_values(args, arg_data);

// First try with a stack buffer, if that fails use a heap buffer
const int BUFSIZE=512;
char buffer[BUFSIZE];
int required_size = format(buffer, BUFSIZE, fmt, arg_values);
// Return value is required size EXCLUDING null byte
if (required_size < BUFSIZE) {
bpftrace->out_->message(MessageType::printf, std::string(buffer), false);
} else {
auto buf = std::make_unique<char[]>(required_size+1);
// if for some reason the size is still wrong the string
// will just be silently truncated
format(buf.get(), required_size, fmt, arg_values);
bpftrace->out_->message(MessageType::printf, std::string(buf.get()), false);
}
bpftrace->out_->message(MessageType::printf, format(fmt, arg_values), false);
}

std::vector<std::unique_ptr<IPrintable>> BPFtrace::get_arg_values(const std::vector<Field> &args, uint8_t* arg_data)
Expand Down
5 changes: 3 additions & 2 deletions src/printf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ namespace bpftrace {
std::string verify_format_string(const std::string &fmt, std::vector<Field> args)
{
std::stringstream message;
const std::regex re("%-?[0-9]*(\\.[0-9]+)?[a-zA-Z]+");

auto tokens_begin = std::sregex_iterator(fmt.begin(), fmt.end(), re);
auto tokens_begin = std::sregex_iterator(fmt.begin(),
fmt.end(),
format_specifier_re);
auto tokens_end = std::sregex_iterator();

auto num_tokens = std::distance(tokens_begin, tokens_end);
Expand Down
2 changes: 2 additions & 0 deletions src/printf.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace bpftrace {

const std::regex format_specifier_re("%-?[0-9]*(\\.[0-9]+)?[a-zA-Z]+");

struct Field;

std::string verify_format_string(const std::string& fmt,
Expand Down
15 changes: 15 additions & 0 deletions tests/runtime/call
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ RUN bpftrace -v -e 'struct Foo { int a; char b[10]; } uprobe:testprogs/uprobe_te
EXPECT 123 hello 456 world
TIMEOUT 5

NAME printf_more_arguments
RUN bpftrace -v -e 'BEGIN { printf("%d: %s; %d: %s;; %d: %s;;; %d: %s;;;;\\n", 1, "a", 2, "ab", 3, "abc", 4, "abcd"); exit(); }'
EXPECT 1: a; 2: ab;; 3: abc;;; 4: abcd;;;;
TIMEOUT 5

NAME time
RUN bpftrace -v -e 'i:ms:1 { time("%H:%M:%S\n"); exit();}'
EXPECT [0-9]*:[0-9]*:[0-9]*
Expand Down Expand Up @@ -87,6 +92,11 @@ RUN bpftrace --unsafe -v -e 'i:ms:100 { system("echo 'ok_system'"); exit();}'
EXPECT ok_system
TIMEOUT 5

NAME system_more_args
RUN bpftrace --unsafe -v -e 'i:ms:100 { system("echo %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7); exit();}'
EXPECT 1 2 3 4 5 6 7
TIMEOUT 5

NAME count
RUN bpftrace -v -e 'i:ms:100 { @ = count(); exit();}'
EXPECT @:\s[0-9]+
Expand Down Expand Up @@ -151,6 +161,11 @@ RUN bpftrace -v -e 'i:ms:1 { cat("/proc/uptime"); exit();}'
EXPECT [0-9]*.[0-9]* [0-9]*.[0-9]*
TIMEOUT 5

NAME cat_more_args
RUN bpftrace -v -e 'i:ms:1 { cat("/%s%s%s%s/%s%s%s", "p", "r", "o", "c", "u", "p", "time"); exit();}'
EXPECT [0-9]*.[0-9]* [0-9]*.[0-9]*
TIMEOUT 5

NAME uaddr
RUN bpftrace -v -e 'uprobe:testprogs/uprobe_test:function1 { printf("0x%lx -- 0x%lx\n", *uaddr("GLOBAL_A"), *uaddr("GLOBAL_C")); exit(); }' -c ./testprogs/uprobe_test
EXPECT 0x55555555 -- 0x33333333
Expand Down
3 changes: 3 additions & 0 deletions tests/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,9 @@ TEST(semantic_analyser, printf)
test("kprobe:f { printf(\"%A\", comm) }", 10);
test("kprobe:f { @x = printf(\"hi\") }", 1);
test("kprobe:f { $x = printf(\"hi\") }", 1);
test("kprobe:f { printf(\"%d %d %d %d %d %d %d %d %d\", 1, 2, 3, 4, 5, 6, 7, "
"8, 9); }",
0);
}

TEST(semantic_analyser, system)
Expand Down

0 comments on commit f2cdf87

Please sign in to comment.