Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
B
bpftrace
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
bpftrace
Commits
ffd94110
Commit
ffd94110
authored
Aug 27, 2018
by
Brendan Gregg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add async functions: print(), clear(), time(), exit()
parent
cf4a4c26
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
676 additions
and
15 deletions
+676
-15
README.md
README.md
+5
-1
src/ast/codegen_llvm.cpp
src/ast/codegen_llvm.cpp
+106
-0
src/ast/semantic_analyser.cpp
src/ast/semantic_analyser.cpp
+60
-0
src/bpftrace.cpp
src/bpftrace.cpp
+215
-11
src/bpftrace.h
src/bpftrace.h
+9
-3
src/types.cpp
src/types.cpp
+5
-0
src/types.h
src/types.h
+12
-0
tests/codegen.cpp
tests/codegen.cpp
+222
-0
tests/semantic_analyser.cpp
tests/semantic_analyser.cpp
+42
-0
No files found.
README.md
View file @
ffd94110
...
...
@@ -205,7 +205,11 @@ Functions:
-
`count()`
- Count the number of times this function is called
-
`delete(@x)`
- Delete the map element passed in as an argument
-
`str(char *s)`
- Returns the string pointed to by
`s`
-
`printf(char *fmt, ...)`
- Write to stdout
-
`printf(char *fmt, ...)`
- Print formatted to stdout
-
`print(@x[, int top [, int div]])`
- Print a map, with optional top entry count and divisor
-
`clear(@x)`
- Delet all key/values from a map
-
`sym(void *p)`
- Resolve kernel address
-
`usym(void *p)`
- Resolve user space address (incomplete)
-
`reg(char *name)`
- Returns the value stored in the named register
-
`time(char *fmt)`
- Print the current time
-
`exit()`
- Quit bpftrace
src/ast/codegen_llvm.cpp
View file @
ffd94110
...
...
@@ -3,6 +3,7 @@
#include "ast.h"
#include "parser.tab.hh"
#include "arch/arch.h"
#include "types.h"
#include <llvm/Support/raw_os_ostream.h>
#include <llvm/Support/TargetRegistry.h>
...
...
@@ -173,6 +174,12 @@ void CodegenLLVM::visit(Call &call)
}
else
if
(
call
.
func
==
"printf"
)
{
/*
* perf event output has: uint64_t printf_id, vargs
* The printf_id maps to bpftrace_.printf_args_, and is a way to define the
* types and offsets of each of the arguments, and share that between BPF and
* user-space for printing.
*/
ArrayType
*
string_type
=
ArrayType
::
get
(
b_
.
getInt8Ty
(),
STRING_SIZE
);
StructType
*
printf_struct
=
StructType
::
create
(
module_
->
getContext
(),
"printf_t"
);
std
::
vector
<
llvm
::
Type
*>
elements
=
{
b_
.
getInt64Ty
()
};
// printf ID
...
...
@@ -207,6 +214,105 @@ void CodegenLLVM::visit(Call &call)
b_
.
CreateLifetimeEnd
(
printf_args
);
expr_
=
nullptr
;
}
else
if
(
call
.
func
==
"exit"
)
{
/*
* perf event output has: uint64_t asyncaction_id
* The asyncaction_id informs user-space that this is not a printf(), but is a
* special asynchronous action. The ID maps to exit().
*/
ArrayType
*
perfdata_type
=
ArrayType
::
get
(
b_
.
getInt8Ty
(),
sizeof
(
uint64_t
));
AllocaInst
*
perfdata
=
b_
.
CreateAllocaBPF
(
perfdata_type
,
"perfdata"
);
b_
.
CreateStore
(
b_
.
getInt64
(
asyncactionint
(
AsyncAction
::
exit
)),
perfdata
);
b_
.
CreatePerfEventOutput
(
ctx_
,
perfdata
,
sizeof
(
uint64_t
));
b_
.
CreateLifetimeEnd
(
perfdata
);
expr_
=
nullptr
;
}
else
if
(
call
.
func
==
"print"
)
{
/*
* perf event output has: uint64_t asyncaction_id, uint64_t top, uint64_t div, string map_ident
* The asyncaction_id informs user-space that this is not a printf(), but is a
* special asynchronous action. The ID maps to print(). The top argument is either
* a value for truncation, or 0 for everything. The div argument divides the output values
* by this (eg: for use in nanosecond -> millisecond conversions).
* TODO: consider stashing top & div in a printf_args_ like struct, so we don't need to pass
* them here via the perfdata output (which is a little more wasteful than need be: I'm using
* uint64_t's to avoid "misaligned stack access off" errors when juggling uint32_t's).
*/
auto
&
arg
=
*
call
.
vargs
->
at
(
0
);
auto
&
map
=
static_cast
<
Map
&>
(
arg
);
Constant
*
const_str
=
ConstantDataArray
::
getString
(
module_
->
getContext
(),
map
.
ident
,
true
);
AllocaInst
*
str_buf
=
b_
.
CreateAllocaBPF
(
ArrayType
::
get
(
b_
.
getInt8Ty
(),
map
.
ident
.
length
()),
"str"
);
b_
.
CreateStore
(
b_
.
CreateGEP
(
const_str
,
b_
.
getInt64
(
0
)),
str_buf
);
ArrayType
*
perfdata_type
=
ArrayType
::
get
(
b_
.
getInt8Ty
(),
sizeof
(
uint64_t
)
+
2
*
sizeof
(
uint64_t
)
+
map
.
ident
.
length
());
AllocaInst
*
perfdata
=
b_
.
CreateAllocaBPF
(
perfdata_type
,
"perfdata"
);
// store asyncactionid:
b_
.
CreateStore
(
b_
.
getInt64
(
asyncactionint
(
AsyncAction
::
print
)),
perfdata
);
// store top:
if
(
call
.
vargs
->
size
()
>
1
)
{
Integer
&
top_arg
=
static_cast
<
Integer
&>
(
*
call
.
vargs
->
at
(
1
));
Value
*
top
;
top_arg
.
accept
(
*
this
);
top
=
expr_
;
b_
.
CreateStore
(
top
,
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt32
(
0
),
b_
.
getInt32
(
sizeof
(
uint64_t
))}));
}
else
b_
.
CreateStore
(
b_
.
getInt64
(
0
),
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
))}));
// store top:
if
(
call
.
vargs
->
size
()
>
2
)
{
Integer
&
div_arg
=
static_cast
<
Integer
&>
(
*
call
.
vargs
->
at
(
2
));
Value
*
div
;
div_arg
.
accept
(
*
this
);
div
=
expr_
;
b_
.
CreateStore
(
div
,
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
)
+
sizeof
(
uint64_t
))}));
}
else
b_
.
CreateStore
(
b_
.
getInt64
(
0
),
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
)
+
sizeof
(
uint64_t
))}));
// store map ident:
b_
.
CreateMemCpy
(
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
)
+
2
*
sizeof
(
uint64_t
))}),
str_buf
,
map
.
ident
.
length
(),
1
);
b_
.
CreatePerfEventOutput
(
ctx_
,
perfdata
,
sizeof
(
uint64_t
)
+
2
*
sizeof
(
uint64_t
)
+
map
.
ident
.
length
());
b_
.
CreateLifetimeEnd
(
perfdata
);
expr_
=
nullptr
;
}
else
if
(
call
.
func
==
"clear"
||
call
.
func
==
"zero"
)
{
auto
&
arg
=
*
call
.
vargs
->
at
(
0
);
auto
&
map
=
static_cast
<
Map
&>
(
arg
);
Constant
*
const_str
=
ConstantDataArray
::
getString
(
module_
->
getContext
(),
map
.
ident
,
true
);
AllocaInst
*
str_buf
=
b_
.
CreateAllocaBPF
(
ArrayType
::
get
(
b_
.
getInt8Ty
(),
map
.
ident
.
length
()),
"str"
);
b_
.
CreateStore
(
b_
.
CreateGEP
(
const_str
,
b_
.
getInt64
(
0
)),
str_buf
);
ArrayType
*
perfdata_type
=
ArrayType
::
get
(
b_
.
getInt8Ty
(),
sizeof
(
uint64_t
)
+
map
.
ident
.
length
());
AllocaInst
*
perfdata
=
b_
.
CreateAllocaBPF
(
perfdata_type
,
"perfdata"
);
if
(
call
.
func
==
"clear"
)
b_
.
CreateStore
(
b_
.
getInt64
(
asyncactionint
(
AsyncAction
::
clear
)),
perfdata
);
else
b_
.
CreateStore
(
b_
.
getInt64
(
asyncactionint
(
AsyncAction
::
zero
)),
perfdata
);
b_
.
CreateMemCpy
(
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
))}),
str_buf
,
map
.
ident
.
length
(),
1
);
b_
.
CreatePerfEventOutput
(
ctx_
,
perfdata
,
sizeof
(
uint64_t
)
+
map
.
ident
.
length
());
b_
.
CreateLifetimeEnd
(
perfdata
);
expr_
=
nullptr
;
}
else
if
(
call
.
func
==
"time"
)
{
ArrayType
*
perfdata_type
=
ArrayType
::
get
(
b_
.
getInt8Ty
(),
sizeof
(
uint64_t
)
*
2
);
AllocaInst
*
perfdata
=
b_
.
CreateAllocaBPF
(
perfdata_type
,
"perfdata"
);
b_
.
CreateStore
(
b_
.
getInt64
(
asyncactionint
(
AsyncAction
::
time
)),
perfdata
);
static
int
time_id
=
0
;
b_
.
CreateStore
(
b_
.
getInt64
(
time_id
),
b_
.
CreateGEP
(
perfdata
,
{
b_
.
getInt64
(
0
),
b_
.
getInt64
(
sizeof
(
uint64_t
))}));
time_id
++
;
b_
.
CreatePerfEventOutput
(
ctx_
,
perfdata
,
sizeof
(
uint64_t
)
*
2
);
b_
.
CreateLifetimeEnd
(
perfdata
);
expr_
=
nullptr
;
}
else
{
abort
();
...
...
src/ast/semantic_analyser.cpp
View file @
ffd94110
...
...
@@ -149,6 +149,57 @@ void SemanticAnalyser::visit(Call &call)
call
.
type
=
SizedType
(
Type
::
none
,
0
);
}
else
if
(
call
.
func
==
"exit"
)
{
check_nargs
(
call
,
0
);
}
else
if
(
call
.
func
==
"print"
)
{
check_assignment
(
call
,
false
,
false
);
if
(
check_varargs
(
call
,
1
,
3
))
{
if
(
is_final_pass
())
{
auto
&
arg
=
*
call
.
vargs
->
at
(
0
);
if
(
!
arg
.
is_map
)
err_
<<
"print() expects a map to be provided"
<<
std
::
endl
;
if
(
call
.
vargs
->
size
()
>
1
)
check_arg
(
call
,
Type
::
integer
,
1
,
true
);
if
(
call
.
vargs
->
size
()
>
2
)
check_arg
(
call
,
Type
::
integer
,
2
,
true
);
}
}
}
else
if
(
call
.
func
==
"clear"
)
{
check_assignment
(
call
,
false
,
false
);
check_nargs
(
call
,
1
);
if
(
check_nargs
(
call
,
1
))
{
auto
&
arg
=
*
call
.
vargs
->
at
(
0
);
if
(
!
arg
.
is_map
)
err_
<<
"clear() expects a map to be provided"
<<
std
::
endl
;
}
}
else
if
(
call
.
func
==
"zero"
)
{
check_assignment
(
call
,
false
,
false
);
check_nargs
(
call
,
1
);
if
(
check_nargs
(
call
,
1
))
{
auto
&
arg
=
*
call
.
vargs
->
at
(
0
);
if
(
!
arg
.
is_map
)
err_
<<
"zero() expects a map to be provided"
<<
std
::
endl
;
}
}
else
if
(
call
.
func
==
"time"
)
{
check_assignment
(
call
,
false
,
false
);
if
(
check_varargs
(
call
,
0
,
1
))
{
if
(
is_final_pass
())
{
if
(
call
.
vargs
&&
call
.
vargs
->
size
()
>
0
)
{
check_arg
(
call
,
Type
::
string
,
0
,
true
);
auto
&
fmt_arg
=
*
call
.
vargs
->
at
(
0
);
String
&
fmt
=
static_cast
<
String
&>
(
fmt_arg
);
bpftrace_
.
time_args_
.
push_back
(
fmt
.
str
);
}
else
{
std
::
string
fmt_default
=
"%H:%M:%S
\n
"
;
bpftrace_
.
time_args_
.
push_back
(
fmt_default
.
c_str
());
}
}
}
}
else
{
err_
<<
"Unknown function: '"
<<
call
.
func
<<
"'"
<<
std
::
endl
;
call
.
type
=
SizedType
(
Type
::
none
,
0
);
...
...
@@ -167,6 +218,14 @@ void SemanticAnalyser::visit(Map &map)
auto
search
=
map_key_
.
find
(
map
.
ident
);
if
(
search
!=
map_key_
.
end
())
{
/*
* TODO: this code ensures that map keys are consistent, but
* currently prevents print() and clear() being used, since
* for example "@x[pid] = count(); ... print(@x)" is detected
* as having inconsistent keys. We need a way to do this check
* differently for print() and clear() calls. I've commented it
* out for now - Brendan.
*
if (search->second != key) {
err_ << "Argument mismatch for " << map.ident << ": ";
err_ << "trying to access with arguments: ";
...
...
@@ -175,6 +234,7 @@ void SemanticAnalyser::visit(Map &map)
err_ << search->second.argument_type_list();
err_ << "\n" << std::endl;
}
*/
}
else
{
map_key_
.
insert
({
map
.
ident
,
key
});
...
...
src/bpftrace.cpp
View file @
ffd94110
...
...
@@ -5,6 +5,7 @@
#include <regex>
#include <sstream>
#include <sys/epoll.h>
#include <time.h>
#include "bcc_syms.h"
#include "perf_reader.h"
...
...
@@ -132,7 +133,56 @@ void perf_event_printer(void *cb_cookie, void *data, int size)
auto
bpftrace
=
static_cast
<
BPFtrace
*>
(
cb_cookie
);
auto
printf_id
=
*
static_cast
<
uint64_t
*>
(
data
);
auto
arg_data
=
static_cast
<
uint8_t
*>
(
data
)
+
sizeof
(
uint64_t
);
int
err
;
// async actions
if
(
printf_id
==
asyncactionint
(
AsyncAction
::
exit
))
{
err
=
bpftrace
->
print_maps
();
exit
(
err
);
}
else
if
(
printf_id
==
asyncactionint
(
AsyncAction
::
print
))
{
std
::
string
arg
=
(
const
char
*
)(
static_cast
<
uint8_t
*>
(
data
)
+
sizeof
(
uint64_t
)
+
2
*
sizeof
(
uint64_t
));
uint64_t
top
=
(
uint64_t
)
*
(
static_cast
<
uint64_t
*>
(
data
)
+
sizeof
(
uint64_t
)
/
sizeof
(
uint64_t
));
uint64_t
div
=
(
uint64_t
)
*
(
static_cast
<
uint64_t
*>
(
data
)
+
(
sizeof
(
uint64_t
)
+
sizeof
(
uint64_t
))
/
sizeof
(
uint64_t
));
bpftrace
->
print_map_ident
(
arg
,
top
,
div
);
return
;
}
else
if
(
printf_id
==
asyncactionint
(
AsyncAction
::
clear
))
{
std
::
string
arg
=
(
const
char
*
)
arg_data
;
bpftrace
->
clear_map_ident
(
arg
);
return
;
}
else
if
(
printf_id
==
asyncactionint
(
AsyncAction
::
zero
))
{
std
::
string
arg
=
(
const
char
*
)
arg_data
;
bpftrace
->
zero_map_ident
(
arg
);
return
;
}
else
if
(
printf_id
==
asyncactionint
(
AsyncAction
::
time
))
{
char
timestr
[
STRING_SIZE
];
time_t
t
;
struct
tm
*
tmp
;
t
=
time
(
NULL
);
tmp
=
localtime
(
&
t
);
if
(
tmp
==
NULL
)
{
perror
(
"localtime"
);
return
;
}
uint64_t
time_id
=
(
uint64_t
)
*
(
static_cast
<
uint64_t
*>
(
data
)
+
sizeof
(
uint64_t
)
/
sizeof
(
uint64_t
));
auto
fmt
=
bpftrace
->
time_args_
[
time_id
].
c_str
();
if
(
strftime
(
timestr
,
sizeof
(
timestr
),
fmt
,
tmp
)
==
0
)
{
fprintf
(
stderr
,
"strftime returned 0"
);
return
;
}
printf
(
"%s"
,
timestr
);
return
;
}
// printf
auto
fmt
=
std
::
get
<
0
>
(
bpftrace
->
printf_args_
[
printf_id
]).
c_str
();
auto
args
=
std
::
get
<
1
>
(
bpftrace
->
printf_args_
[
printf_id
]);
std
::
vector
<
uint64_t
>
arg_values
;
...
...
@@ -314,9 +364,9 @@ int BPFtrace::print_maps()
IMap
&
map
=
*
mapmap
.
second
.
get
();
int
err
;
if
(
map
.
type_
.
type
==
Type
::
quantize
)
err
=
print_map_quantize
(
map
);
err
=
print_map_quantize
(
map
,
0
,
0
);
else
err
=
print_map
(
map
);
err
=
print_map
(
map
,
0
,
0
);
if
(
err
)
return
err
;
...
...
@@ -325,7 +375,142 @@ int BPFtrace::print_maps()
return
0
;
}
int
BPFtrace
::
print_map
(
IMap
&
map
)
// print a map given an ident string
int
BPFtrace
::
print_map_ident
(
const
std
::
string
&
ident
,
uint32_t
top
,
uint32_t
div
)
{
int
err
=
0
;
for
(
auto
&
mapmap
:
maps_
)
{
IMap
&
map
=
*
mapmap
.
second
.
get
();
if
(
map
.
name_
==
ident
)
{
if
(
map
.
type_
.
type
==
Type
::
quantize
)
err
=
print_map_quantize
(
map
,
top
,
div
);
else
err
=
print_map
(
map
,
top
,
div
);
return
err
;
}
}
return
-
2
;
}
// clear a map (delete all keys) given an ident string
int
BPFtrace
::
clear_map_ident
(
const
std
::
string
&
ident
)
{
int
err
=
0
;
for
(
auto
&
mapmap
:
maps_
)
{
IMap
&
map
=
*
mapmap
.
second
.
get
();
if
(
map
.
name_
==
ident
)
{
err
=
clear_map
(
map
);
return
err
;
}
}
return
-
2
;
}
// zero a map (set all keys to zero) given an ident string
int
BPFtrace
::
zero_map_ident
(
const
std
::
string
&
ident
)
{
int
err
=
0
;
for
(
auto
&
mapmap
:
maps_
)
{
IMap
&
map
=
*
mapmap
.
second
.
get
();
if
(
map
.
name_
==
ident
)
{
err
=
zero_map
(
map
);
return
err
;
}
}
return
-
2
;
}
// clear a map
int
BPFtrace
::
clear_map
(
IMap
&
map
)
{
std
::
vector
<
uint8_t
>
old_key
;
try
{
if
(
map
.
type_
.
type
==
Type
::
quantize
)
// quantize maps have 8 extra bytes for the bucket number
old_key
=
find_empty_key
(
map
,
map
.
key_
.
size
()
+
8
);
else
old_key
=
find_empty_key
(
map
,
map
.
key_
.
size
());
}
catch
(
std
::
runtime_error
&
e
)
{
std
::
cerr
<<
"Error getting key for map '"
<<
map
.
name_
<<
"': "
<<
e
.
what
()
<<
std
::
endl
;
return
-
2
;
}
auto
key
(
old_key
);
// snapshot keys, then operate on them
std
::
vector
<
std
::
vector
<
uint8_t
>>
keys
;
while
(
bpf_get_next_key
(
map
.
mapfd_
,
old_key
.
data
(),
key
.
data
())
==
0
)
{
keys
.
push_back
(
key
);
old_key
=
key
;
}
for
(
auto
&
key
:
keys
)
{
int
err
=
bpf_delete_elem
(
map
.
mapfd_
,
key
.
data
());
if
(
err
)
{
std
::
cerr
<<
"Error looking up elem: "
<<
err
<<
std
::
endl
;
return
-
1
;
}
}
return
0
;
}
// zero a map
int
BPFtrace
::
zero_map
(
IMap
&
map
)
{
std
::
vector
<
uint8_t
>
old_key
;
try
{
if
(
map
.
type_
.
type
==
Type
::
quantize
)
// quantize maps have 8 extra bytes for the bucket number
old_key
=
find_empty_key
(
map
,
map
.
key_
.
size
()
+
8
);
else
old_key
=
find_empty_key
(
map
,
map
.
key_
.
size
());
}
catch
(
std
::
runtime_error
&
e
)
{
std
::
cerr
<<
"Error getting key for map '"
<<
map
.
name_
<<
"': "
<<
e
.
what
()
<<
std
::
endl
;
return
-
2
;
}
auto
key
(
old_key
);
// snapshot keys, then operate on them
std
::
vector
<
std
::
vector
<
uint8_t
>>
keys
;
while
(
bpf_get_next_key
(
map
.
mapfd_
,
old_key
.
data
(),
key
.
data
())
==
0
)
{
keys
.
push_back
(
key
);
old_key
=
key
;
}
uint64_t
zero
=
0
;
for
(
auto
&
key
:
keys
)
{
int
err
=
bpf_update_elem
(
map
.
mapfd_
,
key
.
data
(),
&
zero
,
BPF_EXIST
);
if
(
err
)
{
std
::
cerr
<<
"Error looking up elem: "
<<
err
<<
std
::
endl
;
return
-
1
;
}
}
return
0
;
}
int
BPFtrace
::
print_map
(
IMap
&
map
,
uint32_t
top
,
uint32_t
div
)
{
std
::
vector
<
uint8_t
>
old_key
;
try
...
...
@@ -372,11 +557,20 @@ int BPFtrace::print_map(IMap &map)
sort_by_key
(
map
.
key_
.
args_
,
values_by_key
);
};
if
(
div
==
0
)
div
=
1
;
uint32_t
i
=
0
;
for
(
auto
&
pair
:
values_by_key
)
{
auto
key
=
pair
.
first
;
auto
value
=
pair
.
second
;
if
(
top
)
{
if
(
i
++
<
(
values_by_key
.
size
()
-
top
))
continue
;
}
std
::
cout
<<
map
.
name_
<<
map
.
key_
.
argument_value_list
(
*
this
,
key
)
<<
": "
;
if
(
map
.
type_
.
type
==
Type
::
stack
)
...
...
@@ -390,9 +584,9 @@ int BPFtrace::print_map(IMap &map)
else
if
(
map
.
type_
.
type
==
Type
::
string
)
std
::
cout
<<
value
.
data
()
<<
std
::
endl
;
else
if
(
map
.
type_
.
type
==
Type
::
count
)
std
::
cout
<<
reduce_value
(
value
,
ncpus_
)
<<
std
::
endl
;
std
::
cout
<<
reduce_value
(
value
,
ncpus_
)
/
div
<<
std
::
endl
;
else
std
::
cout
<<
*
(
int64_t
*
)
value
.
data
()
<<
std
::
endl
;
std
::
cout
<<
*
(
int64_t
*
)
value
.
data
()
/
div
<<
std
::
endl
;
}
std
::
cout
<<
std
::
endl
;
...
...
@@ -400,7 +594,7 @@ int BPFtrace::print_map(IMap &map)
return
0
;
}
int
BPFtrace
::
print_map_quantize
(
IMap
&
map
)
int
BPFtrace
::
print_map_quantize
(
IMap
&
map
,
uint32_t
top
,
uint32_t
div
)
{
// A quantize-map adds an extra 8 bytes onto the end of its key for storing
// the bucket number.
...
...
@@ -465,13 +659,23 @@ int BPFtrace::print_map_quantize(IMap &map)
return
a
.
second
<
b
.
second
;
});
if
(
div
==
0
)
div
=
1
;
uint32_t
i
=
0
;
for
(
auto
&
key_count
:
total_counts_by_key
)
{
auto
&
key
=
key_count
.
first
;
auto
&
value
=
values_by_key
[
key
];
if
(
top
)
{
if
(
i
++
<
(
values_by_key
.
size
()
-
top
))
continue
;
}
std
::
cout
<<
map
.
name_
<<
map
.
key_
.
argument_value_list
(
*
this
,
key
)
<<
": "
<<
std
::
endl
;
print_quantize
(
value
);
print_quantize
(
value
,
div
);
std
::
cout
<<
std
::
endl
;
}
...
...
@@ -479,14 +683,14 @@ int BPFtrace::print_map_quantize(IMap &map)
return
0
;
}
int
BPFtrace
::
print_quantize
(
const
std
::
vector
<
uint64_t
>
&
values
)
const
int
BPFtrace
::
print_quantize
(
const
std
::
vector
<
uint64_t
>
&
values
,
uint32_t
div
)
const
{
int
max_index
=
-
1
;
int
max_value
=
0
;
for
(
size_t
i
=
0
;
i
<
values
.
size
();
i
++
)
{
int
v
=
values
.
at
(
i
);
int
v
=
values
.
at
(
i
)
/
div
;
if
(
v
!=
0
)
max_index
=
i
;
if
(
v
>
max_value
)
...
...
@@ -510,11 +714,11 @@ int BPFtrace::print_quantize(const std::vector<uint64_t> &values) const
}
int
max_width
=
52
;
int
bar_width
=
values
.
at
(
i
)
/
(
float
)
max_value
*
max_width
;
int
bar_width
=
values
.
at
(
i
)
/
(
(
float
)
max_value
*
max_width
*
div
)
;
std
::
string
bar
(
bar_width
,
'@'
);
std
::
cout
<<
std
::
setw
(
16
)
<<
std
::
left
<<
header
.
str
()
<<
std
::
setw
(
8
)
<<
std
::
right
<<
values
.
at
(
i
)
<<
std
::
setw
(
8
)
<<
std
::
right
<<
(
values
.
at
(
i
)
/
div
)
<<
" |"
<<
std
::
setw
(
max_width
)
<<
std
::
left
<<
bar
<<
"|"
<<
std
::
endl
;
}
...
...
src/bpftrace.h
View file @
ffd94110
...
...
@@ -31,6 +31,9 @@ public:
int
num_probes
()
const
;
int
run
(
std
::
unique_ptr
<
BpfOrc
>
bpforc
);
int
print_maps
();
int
print_map_ident
(
const
std
::
string
&
ident
,
uint32_t
top
,
uint32_t
div
);
int
clear_map_ident
(
const
std
::
string
&
ident
);
int
zero_map_ident
(
const
std
::
string
&
ident
);
std
::
string
get_stack
(
uint32_t
stackid
,
bool
ustack
,
int
indent
=
0
);
std
::
string
resolve_sym
(
uintptr_t
addr
,
bool
show_offset
=
false
);
std
::
string
resolve_usym
(
uintptr_t
addr
)
const
;
...
...
@@ -38,6 +41,7 @@ public:
std
::
map
<
std
::
string
,
std
::
unique_ptr
<
IMap
>>
maps_
;
std
::
map
<
std
::
string
,
Struct
>
structs_
;
std
::
vector
<
std
::
tuple
<
std
::
string
,
std
::
vector
<
SizedType
>>>
printf_args_
;
std
::
vector
<
std
::
string
>
time_args_
;
std
::
unique_ptr
<
IMap
>
stackid_map_
;
std
::
unique_ptr
<
IMap
>
perf_event_map_
;
...
...
@@ -59,9 +63,11 @@ private:
std
::
unique_ptr
<
AttachedProbe
>
attach_probe
(
Probe
&
probe
,
const
BpfOrc
&
bpforc
);
int
setup_perf_events
();
void
poll_perf_events
(
int
epollfd
,
int
timeout
=-
1
);
int
print_map
(
IMap
&
map
);
int
print_map_quantize
(
IMap
&
map
);
int
print_quantize
(
const
std
::
vector
<
uint64_t
>
&
values
)
const
;
int
clear_map
(
IMap
&
map
);
int
zero_map
(
IMap
&
map
);
int
print_map
(
IMap
&
map
,
uint32_t
top
,
uint32_t
div
);
int
print_map_quantize
(
IMap
&
map
,
uint32_t
top
,
uint32_t
div
);
int
print_quantize
(
const
std
::
vector
<
uint64_t
>
&
values
,
uint32_t
div
)
const
;
static
uint64_t
reduce_value
(
const
std
::
vector
<
uint8_t
>
&
value
,
int
ncpus
);
static
std
::
string
quantize_index_label
(
int
power
);
std
::
vector
<
uint8_t
>
find_empty_key
(
IMap
&
map
,
size_t
size
)
const
;
...
...
src/types.cpp
View file @
ffd94110
...
...
@@ -60,4 +60,9 @@ ProbeType probetype(const std::string &type)
abort
();
}
uint64_t
asyncactionint
(
AsyncAction
a
)
{
return
(
uint64_t
)
a
;
}
}
// namespace bpftrace
src/types.h
View file @
ffd94110
...
...
@@ -67,4 +67,16 @@ public:
int
freq
;
};
enum
class
AsyncAction
{
// printf reserves 0-9999 for printf_ids
exit
=
10000
,
print
,
clear
,
zero
,
time
,
};
uint64_t
asyncactionint
(
AsyncAction
a
);
}
// namespace bpftrace
tests/codegen.cpp
View file @
ffd94110
...
...
@@ -948,6 +948,228 @@ attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
call_exit
)
{
test
(
"kprobe:f { exit() }"
,
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [8 x i8], align 8
%1 = getelementptr inbounds [8 x i8], [8 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10000, [8 x i8]* %perfdata, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = tail 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, [8 x i8]* nonnull %perfdata, i64 8)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
call_print
)
{
test
(
"BEGIN { @x = 1; } kprobe:f { print(@x); }"
,
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [26 x i8], align 8
%1 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10001, [26 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 8
%3 = getelementptr inbounds [26 x i8], [26 x i8]* %perfdata, i64 0, i64 24
%4 = bitcast i8* %3 to i16*
call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 8, i1 false)
store i16 30784, i16* %4, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%get_cpu_id = tail 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, [26 x i8]* nonnull %perfdata, i64 26)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
call_clear
)
{
test
(
"BEGIN { @x = 1; } kprobe:f { clear(@x); }"
,
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [10 x i8], align 8
%1 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10002, [10 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 8
%3 = bitcast i8* %2 to i16*
store i16 30784, i16* %3, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%get_cpu_id = tail 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, [10 x i8]* nonnull %perfdata, i64 10)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
call_zero
)
{
test
(
"BEGIN { @x = 1; } kprobe:f { zero(@x); }"
,
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca i64, align 8
%1 = bitcast i64* %"@x_key" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 0, i64* %"@x_key", align 8
%2 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2)
store i64 1, i64* %"@x_val", align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [10 x i8], align 8
%1 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10003, [10 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [10 x i8], [10 x i8]* %perfdata, i64 0, i64 8
%3 = bitcast i8* %2 to i16*
store i16 30784, i16* %3, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2)
%get_cpu_id = tail 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, [10 x i8]* nonnull %perfdata, i64 10)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
call_time
)
{
test
(
"kprobe:f { time(); }"
,
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f" {
entry:
%perfdata = alloca [16 x i8], align 8
%1 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
store i64 10004, [16 x i8]* %perfdata, align 8
%2 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 8
store i64 0, i8* %2, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%get_cpu_id = tail 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, [16 x i8]* nonnull %perfdata, i64 16)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED"
);
}
TEST
(
codegen
,
int_propagation
)
{
test
(
"kprobe:f { @x = 1234; @y = @x }"
,
...
...
tests/semantic_analyser.cpp
View file @
ffd94110
...
...
@@ -81,6 +81,11 @@ TEST(semantic_analyser, builtin_functions)
test
(
"kprobe:f { @x = quantize(123) }"
,
0
);
test
(
"kprobe:f { @x = count() }"
,
0
);
test
(
"kprobe:f { @x = 1; delete(@x) }"
,
0
);
test
(
"kprobe:f { @x = 1; print(@x) }"
,
0
);
test
(
"kprobe:f { @x = 1; clear(@x) }"
,
0
);
test
(
"kprobe:f { @x = 1; zero(@x) }"
,
0
);
test
(
"kprobe:f { time() }"
,
0
);
test
(
"kprobe:f { exit() }"
,
0
);
test
(
"kprobe:f { str(0xffff) }"
,
0
);
test
(
"kprobe:f { printf(
\"
hello
\\
n
\"
) }"
,
0
);
test
(
"kprobe:f { sym(0xffff) }"
,
0
);
...
...
@@ -141,6 +146,43 @@ TEST(semantic_analyser, call_delete)
test
(
"kprobe:f { $y = delete(@x); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_exit
)
{
test
(
"kprobe:f { exit(); }"
,
0
);
test
(
"kprobe:f { exit(1); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_print
)
{
test
(
"kprobe:f { @x = count(); print(@x); }"
,
0
);
test
(
"kprobe:f { @x = count(); print(@x, 5); }"
,
0
);
test
(
"kprobe:f { @x = count(); print(@x, 5, 10); }"
,
0
);
test
(
"kprobe:f { @x = count(); print(@x, 5, 10, 1); }"
,
1
);
test
(
"kprobe:f { @x = count(); @x = print(); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_clear
)
{
test
(
"kprobe:f { @x = count(); clear(@x); }"
,
0
);
test
(
"kprobe:f { @x = count(); clear(@x, 1); }"
,
1
);
test
(
"kprobe:f { @x = count(); @x = clear(); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_zero
)
{
test
(
"kprobe:f { @x = count(); zero(@x); }"
,
0
);
test
(
"kprobe:f { @x = count(); zero(@x, 1); }"
,
1
);
test
(
"kprobe:f { @x = count(); @x = zero(); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_time
)
{
test
(
"kprobe:f { time(); }"
,
0
);
test
(
"kprobe:f { time(
\"
%M:%S
\"
); }"
,
0
);
test
(
"kprobe:f { time(
\"
%M:%S
\"
, 1); }"
,
1
);
test
(
"kprobe:f { @x = time(); }"
,
1
);
}
TEST
(
semantic_analyser
,
call_str
)
{
test
(
"kprobe:f { str(arg0); }"
,
0
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment