|
|
|
|
@@ -74,7 +74,8 @@
|
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
|
|
#define INIT_OPS_HASH(opsname) \
|
|
|
|
|
.func_hash = &opsname.local_hash, \
|
|
|
|
|
.local_hash.regex_lock = __MUTEX_INITIALIZER(opsname.local_hash.regex_lock),
|
|
|
|
|
.local_hash.regex_lock = __MUTEX_INITIALIZER(opsname.local_hash.regex_lock), \
|
|
|
|
|
.subop_list = LIST_HEAD_INIT(opsname.subop_list),
|
|
|
|
|
#else
|
|
|
|
|
#define INIT_OPS_HASH(opsname)
|
|
|
|
|
#endif
|
|
|
|
|
@@ -161,6 +162,7 @@ static inline void ftrace_ops_init(struct ftrace_ops *ops)
|
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
|
|
|
if (!(ops->flags & FTRACE_OPS_FL_INITIALIZED)) {
|
|
|
|
|
mutex_init(&ops->local_hash.regex_lock);
|
|
|
|
|
INIT_LIST_HEAD(&ops->subop_list);
|
|
|
|
|
ops->func_hash = &ops->local_hash;
|
|
|
|
|
ops->flags |= FTRACE_OPS_FL_INITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
@@ -3164,6 +3166,403 @@ out:
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Simply make a copy of @src and return it */
|
|
|
|
|
static struct ftrace_hash *copy_hash(struct ftrace_hash *src)
|
|
|
|
|
{
|
|
|
|
|
if (ftrace_hash_empty(src))
|
|
|
|
|
return EMPTY_HASH;
|
|
|
|
|
|
|
|
|
|
return alloc_and_copy_ftrace_hash(src->size_bits, src);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Append @new_hash entries to @hash:
|
|
|
|
|
*
|
|
|
|
|
* If @hash is the EMPTY_HASH then it traces all functions and nothing
|
|
|
|
|
* needs to be done.
|
|
|
|
|
*
|
|
|
|
|
* If @new_hash is the EMPTY_HASH, then make *hash the EMPTY_HASH so
|
|
|
|
|
* that it traces everything.
|
|
|
|
|
*
|
|
|
|
|
* Otherwise, go through all of @new_hash and add anything that @hash
|
|
|
|
|
* doesn't already have, to @hash.
|
|
|
|
|
*
|
|
|
|
|
* The filter_hash updates uses just the append_hash() function
|
|
|
|
|
* and the notrace_hash does not.
|
|
|
|
|
*/
|
|
|
|
|
static int append_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_func_entry *entry;
|
|
|
|
|
int size;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* An empty hash does everything */
|
|
|
|
|
if (ftrace_hash_empty(*hash))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* If new_hash has everything make hash have everything */
|
|
|
|
|
if (ftrace_hash_empty(new_hash)) {
|
|
|
|
|
free_ftrace_hash(*hash);
|
|
|
|
|
*hash = EMPTY_HASH;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size = 1 << new_hash->size_bits;
|
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
|
hlist_for_each_entry(entry, &new_hash->buckets[i], hlist) {
|
|
|
|
|
/* Only add if not already in hash */
|
|
|
|
|
if (!__ftrace_lookup_ip(*hash, entry->ip) &&
|
|
|
|
|
add_hash_entry(*hash, entry->ip) == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Add to @hash only those that are in both @new_hash1 and @new_hash2
|
|
|
|
|
*
|
|
|
|
|
* The notrace_hash updates uses just the intersect_hash() function
|
|
|
|
|
* and the filter_hash does not.
|
|
|
|
|
*/
|
|
|
|
|
static int intersect_hash(struct ftrace_hash **hash, struct ftrace_hash *new_hash1,
|
|
|
|
|
struct ftrace_hash *new_hash2)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_func_entry *entry;
|
|
|
|
|
int size;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If new_hash1 or new_hash2 is the EMPTY_HASH then make the hash
|
|
|
|
|
* empty as well as empty for notrace means none are notraced.
|
|
|
|
|
*/
|
|
|
|
|
if (ftrace_hash_empty(new_hash1) || ftrace_hash_empty(new_hash2)) {
|
|
|
|
|
free_ftrace_hash(*hash);
|
|
|
|
|
*hash = EMPTY_HASH;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size = 1 << new_hash1->size_bits;
|
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
|
hlist_for_each_entry(entry, &new_hash1->buckets[i], hlist) {
|
|
|
|
|
/* Only add if in both @new_hash1 and @new_hash2 */
|
|
|
|
|
if (__ftrace_lookup_ip(new_hash2, entry->ip) &&
|
|
|
|
|
add_hash_entry(*hash, entry->ip) == NULL)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* If nothing intersects, make it the empty set */
|
|
|
|
|
if (ftrace_hash_empty(*hash)) {
|
|
|
|
|
free_ftrace_hash(*hash);
|
|
|
|
|
*hash = EMPTY_HASH;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Return a new hash that has a union of all @ops->filter_hash entries */
|
|
|
|
|
static struct ftrace_hash *append_hashes(struct ftrace_ops *ops)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_hash *new_hash;
|
|
|
|
|
struct ftrace_ops *subops;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
new_hash = alloc_ftrace_hash(ops->func_hash->filter_hash->size_bits);
|
|
|
|
|
if (!new_hash)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(subops, &ops->subop_list, list) {
|
|
|
|
|
ret = append_hash(&new_hash, subops->func_hash->filter_hash);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
free_ftrace_hash(new_hash);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
/* Nothing more to do if new_hash is empty */
|
|
|
|
|
if (ftrace_hash_empty(new_hash))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return new_hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Make @ops trace evenything except what all its subops do not trace */
|
|
|
|
|
static struct ftrace_hash *intersect_hashes(struct ftrace_ops *ops)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_hash *new_hash = NULL;
|
|
|
|
|
struct ftrace_ops *subops;
|
|
|
|
|
int size_bits;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(subops, &ops->subop_list, list) {
|
|
|
|
|
struct ftrace_hash *next_hash;
|
|
|
|
|
|
|
|
|
|
if (!new_hash) {
|
|
|
|
|
size_bits = subops->func_hash->notrace_hash->size_bits;
|
|
|
|
|
new_hash = alloc_and_copy_ftrace_hash(size_bits, ops->func_hash->notrace_hash);
|
|
|
|
|
if (!new_hash)
|
|
|
|
|
return NULL;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
size_bits = new_hash->size_bits;
|
|
|
|
|
next_hash = new_hash;
|
|
|
|
|
new_hash = alloc_ftrace_hash(size_bits);
|
|
|
|
|
ret = intersect_hash(&new_hash, next_hash, subops->func_hash->notrace_hash);
|
|
|
|
|
free_ftrace_hash(next_hash);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
free_ftrace_hash(new_hash);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
/* Nothing more to do if new_hash is empty */
|
|
|
|
|
if (ftrace_hash_empty(new_hash))
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return new_hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool ops_equal(struct ftrace_hash *A, struct ftrace_hash *B)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_func_entry *entry;
|
|
|
|
|
int size;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (ftrace_hash_empty(A))
|
|
|
|
|
return ftrace_hash_empty(B);
|
|
|
|
|
|
|
|
|
|
if (ftrace_hash_empty(B))
|
|
|
|
|
return ftrace_hash_empty(A);
|
|
|
|
|
|
|
|
|
|
if (A->count != B->count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
size = 1 << A->size_bits;
|
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
|
hlist_for_each_entry(entry, &A->buckets[i], hlist) {
|
|
|
|
|
if (!__ftrace_lookup_ip(B, entry->ip))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ftrace_hash_move_and_update_ops(struct ftrace_ops *ops,
|
|
|
|
|
struct ftrace_hash **orig_hash,
|
|
|
|
|
struct ftrace_hash *hash,
|
|
|
|
|
int enable);
|
|
|
|
|
|
|
|
|
|
static int ftrace_update_ops(struct ftrace_ops *ops, struct ftrace_hash *filter_hash,
|
|
|
|
|
struct ftrace_hash *notrace_hash)
|
|
|
|
|
{
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (!ops_equal(filter_hash, ops->func_hash->filter_hash)) {
|
|
|
|
|
ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->filter_hash,
|
|
|
|
|
filter_hash, 1);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ops_equal(notrace_hash, ops->func_hash->notrace_hash)) {
|
|
|
|
|
ret = ftrace_hash_move_and_update_ops(ops, &ops->func_hash->notrace_hash,
|
|
|
|
|
notrace_hash, 0);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ftrace_startup_subops - enable tracing for subops of an ops
|
|
|
|
|
* @ops: Manager ops (used to pick all the functions of its subops)
|
|
|
|
|
* @subops: A new ops to add to @ops
|
|
|
|
|
* @command: Extra commands to use to enable tracing
|
|
|
|
|
*
|
|
|
|
|
* The @ops is a manager @ops that has the filter that includes all the functions
|
|
|
|
|
* that its list of subops are tracing. Adding a new @subops will add the
|
|
|
|
|
* functions of @subops to @ops.
|
|
|
|
|
*/
|
|
|
|
|
int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_hash *filter_hash;
|
|
|
|
|
struct ftrace_hash *notrace_hash;
|
|
|
|
|
struct ftrace_hash *save_filter_hash;
|
|
|
|
|
struct ftrace_hash *save_notrace_hash;
|
|
|
|
|
int size_bits;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (unlikely(ftrace_disabled))
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
|
|
ftrace_ops_init(ops);
|
|
|
|
|
ftrace_ops_init(subops);
|
|
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(subops->flags & FTRACE_OPS_FL_ENABLED))
|
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
|
|
/* Make everything canonical (Just in case!) */
|
|
|
|
|
if (!ops->func_hash->filter_hash)
|
|
|
|
|
ops->func_hash->filter_hash = EMPTY_HASH;
|
|
|
|
|
if (!ops->func_hash->notrace_hash)
|
|
|
|
|
ops->func_hash->notrace_hash = EMPTY_HASH;
|
|
|
|
|
if (!subops->func_hash->filter_hash)
|
|
|
|
|
subops->func_hash->filter_hash = EMPTY_HASH;
|
|
|
|
|
if (!subops->func_hash->notrace_hash)
|
|
|
|
|
subops->func_hash->notrace_hash = EMPTY_HASH;
|
|
|
|
|
|
|
|
|
|
/* For the first subops to ops just enable it normally */
|
|
|
|
|
if (list_empty(&ops->subop_list)) {
|
|
|
|
|
/* Just use the subops hashes */
|
|
|
|
|
filter_hash = copy_hash(subops->func_hash->filter_hash);
|
|
|
|
|
notrace_hash = copy_hash(subops->func_hash->notrace_hash);
|
|
|
|
|
if (!filter_hash || !notrace_hash) {
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
save_filter_hash = ops->func_hash->filter_hash;
|
|
|
|
|
save_notrace_hash = ops->func_hash->notrace_hash;
|
|
|
|
|
|
|
|
|
|
ops->func_hash->filter_hash = filter_hash;
|
|
|
|
|
ops->func_hash->notrace_hash = notrace_hash;
|
|
|
|
|
list_add(&subops->list, &ops->subop_list);
|
|
|
|
|
ret = ftrace_startup(ops, command);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
list_del(&subops->list);
|
|
|
|
|
ops->func_hash->filter_hash = save_filter_hash;
|
|
|
|
|
ops->func_hash->notrace_hash = save_notrace_hash;
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
} else {
|
|
|
|
|
free_ftrace_hash(save_filter_hash);
|
|
|
|
|
free_ftrace_hash(save_notrace_hash);
|
|
|
|
|
subops->flags |= FTRACE_OPS_FL_ENABLED;
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Here there's already something attached. Here are the rules:
|
|
|
|
|
* o If either filter_hash is empty then the final stays empty
|
|
|
|
|
* o Otherwise, the final is a superset of both hashes
|
|
|
|
|
* o If either notrace_hash is empty then the final stays empty
|
|
|
|
|
* o Otherwise, the final is an intersection between the hashes
|
|
|
|
|
*/
|
|
|
|
|
if (ftrace_hash_empty(ops->func_hash->filter_hash) ||
|
|
|
|
|
ftrace_hash_empty(subops->func_hash->filter_hash)) {
|
|
|
|
|
filter_hash = EMPTY_HASH;
|
|
|
|
|
} else {
|
|
|
|
|
size_bits = max(ops->func_hash->filter_hash->size_bits,
|
|
|
|
|
subops->func_hash->filter_hash->size_bits);
|
|
|
|
|
filter_hash = alloc_and_copy_ftrace_hash(size_bits, ops->func_hash->filter_hash);
|
|
|
|
|
if (!filter_hash)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
ret = append_hash(&filter_hash, subops->func_hash->filter_hash);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ftrace_hash_empty(ops->func_hash->notrace_hash) ||
|
|
|
|
|
ftrace_hash_empty(subops->func_hash->notrace_hash)) {
|
|
|
|
|
notrace_hash = EMPTY_HASH;
|
|
|
|
|
} else {
|
|
|
|
|
size_bits = max(ops->func_hash->filter_hash->size_bits,
|
|
|
|
|
subops->func_hash->filter_hash->size_bits);
|
|
|
|
|
notrace_hash = alloc_ftrace_hash(size_bits);
|
|
|
|
|
if (!notrace_hash) {
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = intersect_hash(¬race_hash, ops->func_hash->filter_hash,
|
|
|
|
|
subops->func_hash->filter_hash);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_add(&subops->list, &ops->subop_list);
|
|
|
|
|
|
|
|
|
|
ret = ftrace_update_ops(ops, filter_hash, notrace_hash);
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
list_del(&subops->list);
|
|
|
|
|
else
|
|
|
|
|
subops->flags |= FTRACE_OPS_FL_ENABLED;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ftrace_shutdown_subops - Remove a subops from a manager ops
|
|
|
|
|
* @ops: A manager ops to remove @subops from
|
|
|
|
|
* @subops: The subops to remove from @ops
|
|
|
|
|
* @command: Any extra command flags to add to modifying the text
|
|
|
|
|
*
|
|
|
|
|
* Removes the functions being traced by the @subops from @ops. Note, it
|
|
|
|
|
* will not affect functions that are being traced by other subops that
|
|
|
|
|
* still exist in @ops.
|
|
|
|
|
*
|
|
|
|
|
* If the last subops is removed from @ops, then @ops is shutdown normally.
|
|
|
|
|
*/
|
|
|
|
|
int ftrace_shutdown_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command)
|
|
|
|
|
{
|
|
|
|
|
struct ftrace_hash *filter_hash;
|
|
|
|
|
struct ftrace_hash *notrace_hash;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (unlikely(ftrace_disabled))
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(!(subops->flags & FTRACE_OPS_FL_ENABLED)))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
list_del(&subops->list);
|
|
|
|
|
|
|
|
|
|
if (list_empty(&ops->subop_list)) {
|
|
|
|
|
/* Last one, just disable the current ops */
|
|
|
|
|
|
|
|
|
|
ret = ftrace_shutdown(ops, command);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
list_add(&subops->list, &ops->subop_list);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
subops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
|
|
|
|
|
|
|
|
free_ftrace_hash(ops->func_hash->filter_hash);
|
|
|
|
|
free_ftrace_hash(ops->func_hash->notrace_hash);
|
|
|
|
|
ops->func_hash->filter_hash = EMPTY_HASH;
|
|
|
|
|
ops->func_hash->notrace_hash = EMPTY_HASH;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Rebuild the hashes without subops */
|
|
|
|
|
filter_hash = append_hashes(ops);
|
|
|
|
|
notrace_hash = intersect_hashes(ops);
|
|
|
|
|
if (!filter_hash || !notrace_hash) {
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
list_add(&subops->list, &ops->subop_list);
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = ftrace_update_ops(ops, filter_hash, notrace_hash);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
list_add(&subops->list, &ops->subop_list);
|
|
|
|
|
else
|
|
|
|
|
subops->flags &= ~FTRACE_OPS_FL_ENABLED;
|
|
|
|
|
|
|
|
|
|
free_ftrace_hash(filter_hash);
|
|
|
|
|
free_ftrace_hash(notrace_hash);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static u64 ftrace_update_time;
|
|
|
|
|
unsigned long ftrace_update_tot_cnt;
|
|
|
|
|
unsigned long ftrace_number_of_pages;
|
|
|
|
|
|