diff options
Diffstat (limited to 'kernel/trace/ftrace.c')
| -rw-r--r-- | kernel/trace/ftrace.c | 52 | 
1 files changed, 44 insertions, 8 deletions
| diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 4d8e35575549..3ba52d4e1314 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -3231,7 +3231,8 @@ ftrace_allocate_pages(unsigned long num_to_init)  	pg = start_pg;  	while (pg) {  		order = get_count_order(pg->size / ENTRIES_PER_PAGE); -		free_pages((unsigned long)pg->records, order); +		if (order >= 0) +			free_pages((unsigned long)pg->records, order);  		start_pg = pg->next;  		kfree(pg);  		pg = start_pg; @@ -5045,6 +5046,20 @@ struct ftrace_direct_func *ftrace_find_direct_func(unsigned long addr)  	return NULL;  } +static struct ftrace_direct_func *ftrace_alloc_direct_func(unsigned long addr) +{ +	struct ftrace_direct_func *direct; + +	direct = kmalloc(sizeof(*direct), GFP_KERNEL); +	if (!direct) +		return NULL; +	direct->addr = addr; +	direct->count = 0; +	list_add_rcu(&direct->next, &ftrace_direct_funcs); +	ftrace_direct_func_count++; +	return direct; +} +  /**   * register_ftrace_direct - Call a custom trampoline directly   * @ip: The address of the nop at the beginning of a function @@ -5120,15 +5135,11 @@ int register_ftrace_direct(unsigned long ip, unsigned long addr)  	direct = ftrace_find_direct_func(addr);  	if (!direct) { -		direct = kmalloc(sizeof(*direct), GFP_KERNEL); +		direct = ftrace_alloc_direct_func(addr);  		if (!direct) {  			kfree(entry);  			goto out_unlock;  		} -		direct->addr = addr; -		direct->count = 0; -		list_add_rcu(&direct->next, &ftrace_direct_funcs); -		ftrace_direct_func_count++;  	}  	entry->ip = ip; @@ -5329,6 +5340,7 @@ int __weak ftrace_modify_direct_caller(struct ftrace_func_entry *entry,  int modify_ftrace_direct(unsigned long ip,  			 unsigned long old_addr, unsigned long new_addr)  { +	struct ftrace_direct_func *direct, *new_direct = NULL;  	struct ftrace_func_entry *entry;  	struct dyn_ftrace *rec;  	int ret = -ENODEV; @@ -5344,6 +5356,20 @@ int modify_ftrace_direct(unsigned long ip,  	if (entry->direct != old_addr)  		goto out_unlock; +	direct = ftrace_find_direct_func(old_addr); +	if (WARN_ON(!direct)) +		goto out_unlock; +	if (direct->count > 1) { +		ret = -ENOMEM; +		new_direct = ftrace_alloc_direct_func(new_addr); +		if (!new_direct) +			goto out_unlock; +		direct->count--; +		new_direct->count++; +	} else { +		direct->addr = new_addr; +	} +  	/*  	 * If there's no other ftrace callback on the rec->ip location,  	 * then it can be changed directly by the architecture. @@ -5357,6 +5383,14 @@ int modify_ftrace_direct(unsigned long ip,  		ret = 0;  	} +	if (unlikely(ret && new_direct)) { +		direct->count++; +		list_del_rcu(&new_direct->next); +		synchronize_rcu_tasks(); +		kfree(new_direct); +		ftrace_direct_func_count--; +	} +   out_unlock:  	mutex_unlock(&ftrace_lock);  	mutex_unlock(&direct_mutex); @@ -6418,7 +6452,8 @@ void ftrace_release_mod(struct module *mod)  		clear_mod_from_hashes(pg);  		order = get_count_order(pg->size / ENTRIES_PER_PAGE); -		free_pages((unsigned long)pg->records, order); +		if (order >= 0) +			free_pages((unsigned long)pg->records, order);  		tmp_page = pg->next;  		kfree(pg);  		ftrace_number_of_pages -= 1 << order; @@ -6778,7 +6813,8 @@ void ftrace_free_mem(struct module *mod, void *start_ptr, void *end_ptr)  		if (!pg->index) {  			*last_pg = pg->next;  			order = get_count_order(pg->size / ENTRIES_PER_PAGE); -			free_pages((unsigned long)pg->records, order); +			if (order >= 0) +				free_pages((unsigned long)pg->records, order);  			ftrace_number_of_pages -= 1 << order;  			ftrace_number_of_groups--;  			kfree(pg); |