Commit df51b11a authored by Matheus Marchini's avatar Matheus Marchini

promote scalar printf args to 64-bits

Due to BPF stack alignment restrictions, LLVM might change the size used
to stored printf arguments in the stack, resulting in alignment issues
when formatting and outputting those arguments. To avoid this issue, we
promote every non-array argument to 64-bits, assuring the size used to
store the argument is the same used to dereference it later when calling
printf.

Fixes: https://github.com/iovisor/bpftrace/issues/47
parent 75c41dd6
...@@ -428,7 +428,7 @@ void CodegenLLVM::visit(Call &call) ...@@ -428,7 +428,7 @@ void CodegenLLVM::visit(Call &call)
Expression &arg = *call.vargs->at(i); Expression &arg = *call.vargs->at(i);
arg.accept(*this); arg.accept(*this);
Value *offset = b_.CreateGEP(printf_args, {b_.getInt32(0), b_.getInt32(i)}); Value *offset = b_.CreateGEP(printf_args, {b_.getInt32(0), b_.getInt32(i)});
if (arg.type.type == Type::string || arg.type.type == Type::usym) if (arg.type.IsArray())
b_.CreateMemCpy(offset, expr_, arg.type.size, 1); b_.CreateMemCpy(offset, expr_, arg.type.size, 1);
else else
b_.CreateStore(expr_, offset); b_.CreateStore(expr_, offset);
...@@ -477,7 +477,7 @@ void CodegenLLVM::visit(Call &call) ...@@ -477,7 +477,7 @@ void CodegenLLVM::visit(Call &call)
Expression &arg = *call.vargs->at(i); Expression &arg = *call.vargs->at(i);
arg.accept(*this); arg.accept(*this);
Value *offset = b_.CreateGEP(system_args, {b_.getInt32(0), b_.getInt32(i)}); Value *offset = b_.CreateGEP(system_args, {b_.getInt32(0), b_.getInt32(i)});
if (arg.type.type == Type::string || arg.type.type == Type::usym) if (arg.type.IsArray())
b_.CreateMemCpy(offset, expr_, arg.type.size, 1); b_.CreateMemCpy(offset, expr_, arg.type.size, 1);
else else
b_.CreateStore(expr_, offset); b_.CreateStore(expr_, offset);
......
...@@ -73,7 +73,7 @@ AllocaInst *IRBuilderBPF::CreateAllocaBPF(int bytes, const std::string &name) ...@@ -73,7 +73,7 @@ AllocaInst *IRBuilderBPF::CreateAllocaBPF(int bytes, const std::string &name)
llvm::Type *IRBuilderBPF::GetType(const SizedType &stype) llvm::Type *IRBuilderBPF::GetType(const SizedType &stype)
{ {
llvm::Type *ty; llvm::Type *ty;
if (stype.type == Type::string || stype.type == Type::usym || (stype.type == Type::cast && !stype.is_pointer)) if (stype.IsArray())
{ {
ty = ArrayType::get(getInt8Ty(), stype.size); ty = ArrayType::get(getInt8Ty(), stype.size);
} }
......
...@@ -262,7 +262,11 @@ void SemanticAnalyser::visit(Call &call) ...@@ -262,7 +262,11 @@ void SemanticAnalyser::visit(Call &call)
String &fmt = static_cast<String&>(fmt_arg); String &fmt = static_cast<String&>(fmt_arg);
std::vector<Field> args; std::vector<Field> args;
for (auto iter = call.vargs->begin()+1; iter != call.vargs->end(); iter++) { for (auto iter = call.vargs->begin()+1; iter != call.vargs->end(); iter++) {
args.push_back({ .type = (*iter)->type }); auto ty = (*iter)->type;
// Promote to 64-bit if it's not an array type
if (!ty.IsArray())
ty.size = 8;
args.push_back({ .type = ty });
} }
err_ << verify_format_string(fmt.str, args); err_ << verify_format_string(fmt.str, args);
......
...@@ -23,6 +23,11 @@ bool SizedType::operator==(const SizedType &t) const ...@@ -23,6 +23,11 @@ bool SizedType::operator==(const SizedType &t) const
return type == t.type && size == t.size; return type == t.type && size == t.size;
} }
bool SizedType::IsArray() const
{
return type == Type::string || type == Type::usym || (type == Type::cast && !is_pointer);
}
std::string typestr(Type t) std::string typestr(Type t)
{ {
switch (t) switch (t)
......
...@@ -50,6 +50,8 @@ public: ...@@ -50,6 +50,8 @@ public:
bool is_pointer = false; bool is_pointer = false;
size_t pointee_size; size_t pointee_size;
bool IsArray() const;
bool operator==(const SizedType &t) const; bool operator==(const SizedType &t) const;
}; };
......
...@@ -42,6 +42,7 @@ TEST(codegen, printf_offsets) ...@@ -42,6 +42,7 @@ TEST(codegen, printf_offsets)
BPFtrace bpftrace; BPFtrace bpftrace;
Driver driver; Driver driver;
// TODO (mmarchini): also test printf with a string argument
ASSERT_EQ(driver.parse_str("struct Foo { char c; int i; } kprobe:f { $foo = (Foo*)0; printf(\"%c %u\\n\", $foo->c, $foo->i) }"), 0); ASSERT_EQ(driver.parse_str("struct Foo { char c; int i; } kprobe:f { $foo = (Foo*)0; printf(\"%c %u\\n\", $foo->c, $foo->i) }"), 0);
ClangParser clang; ClangParser clang;
clang.parse(driver.root_, bpftrace.structs_); clang.parse(driver.root_, bpftrace.structs_);
...@@ -60,13 +61,15 @@ TEST(codegen, printf_offsets) ...@@ -60,13 +61,15 @@ TEST(codegen, printf_offsets)
EXPECT_EQ(args.size(), 2); EXPECT_EQ(args.size(), 2);
// NOTE (mmarchini) type.size is the original arg size, and it might be
// different from the actual size we use to store in memory
EXPECT_EQ(args[0].type.type, Type::integer); EXPECT_EQ(args[0].type.type, Type::integer);
EXPECT_EQ(args[0].type.size, 1); EXPECT_EQ(args[0].type.size, 8);
EXPECT_EQ(args[0].offset, 8); EXPECT_EQ(args[0].offset, 8);
EXPECT_EQ(args[1].type.type, Type::integer); EXPECT_EQ(args[1].type.type, Type::integer);
EXPECT_EQ(args[1].type.size, 4); EXPECT_EQ(args[1].type.size, 8);
EXPECT_EQ(args[1].offset, 12); EXPECT_EQ(args[1].offset, 16);
} }
std::string header = R"HEAD(; ModuleID = 'bpftrace' std::string header = R"HEAD(; ModuleID = 'bpftrace'
...@@ -1694,7 +1697,7 @@ TEST(codegen, call_printf) ...@@ -1694,7 +1697,7 @@ TEST(codegen, call_printf)
{ {
test("struct Foo { char c; long l; } kprobe:f { $foo = (Foo*)0; printf(\"%c %lu\\n\", $foo->c, $foo->l) }", test("struct Foo { char c; long l; } kprobe:f { $foo = (Foo*)0; printf(\"%c %lu\\n\", $foo->c, $foo->l) }",
R"EXPECTED(%printf_t = type { i64, i8, i64 } R"EXPECTED(%printf_t = type { i64, i64, i64 }
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0 declare i64 @llvm.bpf.pseudo(i64, i64) #0
...@@ -1716,7 +1719,7 @@ entry: ...@@ -1716,7 +1719,7 @@ entry:
%3 = load i8, i8* %Foo.c, align 1 %3 = load i8, i8* %Foo.c, align 1
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %Foo.c) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %Foo.c)
%4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1
store i8 %3, i8* %4, align 8 store i8 %3, i64* %4, align 8
%5 = bitcast i64* %Foo.l to i8* %5 = bitcast i64* %Foo.l to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5)
%probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %Foo.l, i64 8, i64 8) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %Foo.l, i64 8, i64 8)
...@@ -1726,7 +1729,7 @@ entry: ...@@ -1726,7 +1729,7 @@ entry:
store i64 %6, i64* %7, align 8 store i64 %6, i64* %7, align 8
%pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)()
%perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 20) %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 24)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0 ret i64 0
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment