Skip to content

Commit fef5aee

Browse files
committed
ftrace: Replace tramp_hash with old_*_hash to save space
Allowing function callbacks to declare their own trampolines requires that each ftrace_ops that has a trampoline must have some sort of accounting that keeps track of which ops has a trampoline attached to a record. The easy way to solve this was to add a "tramp_hash" that created a hash entry for every function that a ops uses with a trampoline. But since we can have literally tens of thousands of functions being traced, that means we need tens of thousands of descriptors to map the ops to the function in the hash. This is quite expensive and can cause enabling and disabling the function graph tracer to take some time to start and stop. It can take up to several seconds to disable or enable all functions in the function graph tracer for this reason. The better approach albeit more complex, is to keep track of how ops are being enabled and disabled, and use that along with the counting of the number of ops attached to records, to determive what ops has a trampoline attached to a record at enabling and disabling of tracing. To do this, the tramp_hash has been replaced with an old_filter_hash and old_notrace_hash, which get the copy of the ops filter_hash and notrace_hash respectively. The old hashes is kept until the ops has been modified or removed and the old hashes are used with the logic of the accounting to determine the ops that have the trampoline of a record. The reason this has less of a footprint is due to the trick that an "empty" hash in the filter_hash means "all functions" and an empty hash in the notrace hash means "no functions" in the hash. This is much more efficienct, doesn't have the delay, and takes up much less memory, as we do not need to map all the functions but just figure out which functions are mapped at the time it is enabled or disabled. Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
1 parent e1effa0 commit fef5aee

File tree

2 files changed

+85
-156
lines changed

2 files changed

+85
-156
lines changed

include/linux/ftrace.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ struct ftrace_ops {
140140
int nr_trampolines;
141141
struct ftrace_ops_hash local_hash;
142142
struct ftrace_ops_hash *func_hash;
143-
struct ftrace_hash *tramp_hash;
143+
struct ftrace_ops_hash old_hash;
144144
unsigned long trampoline;
145145
#endif
146146
};

kernel/trace/ftrace.c

Lines changed: 84 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,21 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
13731373
return 0;
13741374
}
13751375

1376+
static bool hash_contains_ip(unsigned long ip,
1377+
struct ftrace_ops_hash *hash)
1378+
{
1379+
/*
1380+
* The function record is a match if it exists in the filter
1381+
* hash and not in the notrace hash. Note, an emty hash is
1382+
* considered a match for the filter hash, but an empty
1383+
* notrace hash is considered not in the notrace hash.
1384+
*/
1385+
return (ftrace_hash_empty(hash->filter_hash) ||
1386+
ftrace_lookup_ip(hash->filter_hash, ip)) &&
1387+
(ftrace_hash_empty(hash->notrace_hash) ||
1388+
!ftrace_lookup_ip(hash->notrace_hash, ip));
1389+
}
1390+
13761391
/*
13771392
* Test the hashes for this ops to see if we want to call
13781393
* the ops->func or not.
@@ -1388,8 +1403,7 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable,
13881403
static int
13891404
ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip, void *regs)
13901405
{
1391-
struct ftrace_hash *filter_hash;
1392-
struct ftrace_hash *notrace_hash;
1406+
struct ftrace_ops_hash hash;
13931407
int ret;
13941408

13951409
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
@@ -1402,13 +1416,10 @@ ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip, void *regs)
14021416
return 0;
14031417
#endif
14041418

1405-
filter_hash = rcu_dereference_raw_notrace(ops->func_hash->filter_hash);
1406-
notrace_hash = rcu_dereference_raw_notrace(ops->func_hash->notrace_hash);
1419+
hash.filter_hash = rcu_dereference_raw_notrace(ops->func_hash->filter_hash);
1420+
hash.notrace_hash = rcu_dereference_raw_notrace(ops->func_hash->notrace_hash);
14071421

1408-
if ((ftrace_hash_empty(filter_hash) ||
1409-
ftrace_lookup_ip(filter_hash, ip)) &&
1410-
(ftrace_hash_empty(notrace_hash) ||
1411-
!ftrace_lookup_ip(notrace_hash, ip)))
1422+
if (hash_contains_ip(ip, &hash))
14121423
ret = 1;
14131424
else
14141425
ret = 0;
@@ -1520,46 +1531,6 @@ static bool test_rec_ops_needs_regs(struct dyn_ftrace *rec)
15201531
return keep_regs;
15211532
}
15221533

1523-
static void ftrace_remove_tramp(struct ftrace_ops *ops,
1524-
struct dyn_ftrace *rec)
1525-
{
1526-
/* If TRAMP is not set, no ops should have a trampoline for this */
1527-
if (!(rec->flags & FTRACE_FL_TRAMP))
1528-
return;
1529-
1530-
rec->flags &= ~FTRACE_FL_TRAMP;
1531-
1532-
if ((!ftrace_hash_empty(ops->func_hash->filter_hash) &&
1533-
!ftrace_lookup_ip(ops->func_hash->filter_hash, rec->ip)) ||
1534-
ftrace_lookup_ip(ops->func_hash->notrace_hash, rec->ip))
1535-
return;
1536-
/*
1537-
* The tramp_hash entry will be removed at time
1538-
* of update.
1539-
*/
1540-
ops->nr_trampolines--;
1541-
}
1542-
1543-
static void ftrace_clear_tramps(struct dyn_ftrace *rec, struct ftrace_ops *ops)
1544-
{
1545-
struct ftrace_ops *op;
1546-
1547-
/* If TRAMP is not set, no ops should have a trampoline for this */
1548-
if (!(rec->flags & FTRACE_FL_TRAMP))
1549-
return;
1550-
1551-
do_for_each_ftrace_op(op, ftrace_ops_list) {
1552-
/*
1553-
* This function is called to clear other tramps
1554-
* not the one that is being updated.
1555-
*/
1556-
if (op == ops)
1557-
continue;
1558-
if (op->nr_trampolines)
1559-
ftrace_remove_tramp(op, rec);
1560-
} while_for_each_ftrace_op(op);
1561-
}
1562-
15631534
static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
15641535
int filter_hash,
15651536
bool inc)
@@ -1648,18 +1619,16 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
16481619
* function, and the ops has a trampoline registered
16491620
* for it, then we can call it directly.
16501621
*/
1651-
if (ftrace_rec_count(rec) == 1 && ops->trampoline) {
1622+
if (ftrace_rec_count(rec) == 1 && ops->trampoline)
16521623
rec->flags |= FTRACE_FL_TRAMP;
1653-
ops->nr_trampolines++;
1654-
} else {
1624+
else
16551625
/*
16561626
* If we are adding another function callback
16571627
* to this function, and the previous had a
16581628
* custom trampoline in use, then we need to go
16591629
* back to the default trampoline.
16601630
*/
1661-
ftrace_clear_tramps(rec, ops);
1662-
}
1631+
rec->flags &= ~FTRACE_FL_TRAMP;
16631632

16641633
/*
16651634
* If any ops wants regs saved for this function
@@ -1672,9 +1641,6 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
16721641
return;
16731642
rec->flags--;
16741643

1675-
if (ops->trampoline && !ftrace_rec_count(rec))
1676-
ftrace_remove_tramp(ops, rec);
1677-
16781644
/*
16791645
* If the rec had REGS enabled and the ops that is
16801646
* being removed had REGS set, then see if there is
@@ -1688,6 +1654,17 @@ static void __ftrace_hash_rec_update(struct ftrace_ops *ops,
16881654
rec->flags &= ~FTRACE_FL_REGS;
16891655
}
16901656

1657+
/*
1658+
* If the rec had TRAMP enabled, then it needs to
1659+
* be cleared. As TRAMP can only be enabled iff
1660+
* there is only a single ops attached to it.
1661+
* In otherwords, always disable it on decrementing.
1662+
* In the future, we may set it if rec count is
1663+
* decremented to one, and the ops that is left
1664+
* has a trampoline.
1665+
*/
1666+
rec->flags &= ~FTRACE_FL_TRAMP;
1667+
16911668
/*
16921669
* flags will be cleared in ftrace_check_record()
16931670
* if rec count is zero.
@@ -1910,15 +1887,14 @@ static struct ftrace_ops *
19101887
ftrace_find_tramp_ops_any(struct dyn_ftrace *rec)
19111888
{
19121889
struct ftrace_ops *op;
1890+
unsigned long ip = rec->ip;
19131891

19141892
do_for_each_ftrace_op(op, ftrace_ops_list) {
19151893

19161894
if (!op->trampoline)
19171895
continue;
19181896

1919-
if (ftrace_lookup_ip(op->func_hash->filter_hash, rec->ip) &&
1920-
(ftrace_hash_empty(op->func_hash->notrace_hash) ||
1921-
!ftrace_lookup_ip(op->func_hash->notrace_hash, rec->ip)))
1897+
if (hash_contains_ip(ip, op->func_hash))
19221898
return op;
19231899
} while_for_each_ftrace_op(op);
19241900

@@ -1929,18 +1905,51 @@ static struct ftrace_ops *
19291905
ftrace_find_tramp_ops_curr(struct dyn_ftrace *rec)
19301906
{
19311907
struct ftrace_ops *op;
1908+
unsigned long ip = rec->ip;
19321909

1933-
/* Removed ops need to be tested first */
1934-
if (removed_ops && removed_ops->tramp_hash) {
1935-
if (ftrace_lookup_ip(removed_ops->tramp_hash, rec->ip))
1910+
/*
1911+
* Need to check removed ops first.
1912+
* If they are being removed, and this rec has a tramp,
1913+
* and this rec is in the ops list, then it would be the
1914+
* one with the tramp.
1915+
*/
1916+
if (removed_ops) {
1917+
if (hash_contains_ip(ip, &removed_ops->old_hash))
19361918
return removed_ops;
19371919
}
19381920

1921+
/*
1922+
* Need to find the current trampoline for a rec.
1923+
* Now, a trampoline is only attached to a rec if there
1924+
* was a single 'ops' attached to it. But this can be called
1925+
* when we are adding another op to the rec or removing the
1926+
* current one. Thus, if the op is being added, we can
1927+
* ignore it because it hasn't attached itself to the rec
1928+
* yet. That means we just need to find the op that has a
1929+
* trampoline and is not beeing added.
1930+
*/
19391931
do_for_each_ftrace_op(op, ftrace_ops_list) {
1940-
if (!op->tramp_hash)
1932+
1933+
if (!op->trampoline)
1934+
continue;
1935+
1936+
/*
1937+
* If the ops is being added, it hasn't gotten to
1938+
* the point to be removed from this tree yet.
1939+
*/
1940+
if (op->flags & FTRACE_OPS_FL_ADDING)
19411941
continue;
19421942

1943-
if (ftrace_lookup_ip(op->tramp_hash, rec->ip))
1943+
/*
1944+
* If the ops is not being added and has a trampoline,
1945+
* then it must be the one that we want!
1946+
*/
1947+
if (hash_contains_ip(ip, op->func_hash))
1948+
return op;
1949+
1950+
/* If the ops is being modified, it may be in the old hash. */
1951+
if ((op->flags & FTRACE_OPS_FL_MODIFYING) &&
1952+
hash_contains_ip(ip, &op->old_hash))
19441953
return op;
19451954

19461955
} while_for_each_ftrace_op(op);
@@ -1952,10 +1961,11 @@ static struct ftrace_ops *
19521961
ftrace_find_tramp_ops_new(struct dyn_ftrace *rec)
19531962
{
19541963
struct ftrace_ops *op;
1964+
unsigned long ip = rec->ip;
19551965

19561966
do_for_each_ftrace_op(op, ftrace_ops_list) {
19571967
/* pass rec in as regs to have non-NULL val */
1958-
if (ftrace_ops_test(op, rec->ip, rec))
1968+
if (hash_contains_ip(ip, op->func_hash))
19591969
return op;
19601970
} while_for_each_ftrace_op(op);
19611971

@@ -2262,92 +2272,6 @@ void __weak arch_ftrace_update_code(int command)
22622272
ftrace_run_stop_machine(command);
22632273
}
22642274

2265-
static int ftrace_save_ops_tramp_hash(struct ftrace_ops *ops)
2266-
{
2267-
struct ftrace_page *pg;
2268-
struct dyn_ftrace *rec;
2269-
int size, bits;
2270-
int ret;
2271-
2272-
size = ops->nr_trampolines;
2273-
bits = 0;
2274-
/*
2275-
* Make the hash size about 1/2 the # found
2276-
*/
2277-
for (size /= 2; size; size >>= 1)
2278-
bits++;
2279-
2280-
ops->tramp_hash = alloc_ftrace_hash(bits);
2281-
/*
2282-
* TODO: a failed allocation is going to screw up
2283-
* the accounting of what needs to be modified
2284-
* and not. For now, we kill ftrace if we fail
2285-
* to allocate here. But there are ways around this,
2286-
* but that will take a little more work.
2287-
*/
2288-
if (!ops->tramp_hash)
2289-
return -ENOMEM;
2290-
2291-
do_for_each_ftrace_rec(pg, rec) {
2292-
if (ftrace_rec_count(rec) == 1 &&
2293-
ftrace_ops_test(ops, rec->ip, rec)) {
2294-
2295-
/*
2296-
* If another ops adds to a rec, the rec will
2297-
* lose its trampoline and never get it back
2298-
* until all ops are off of it.
2299-
*/
2300-
if (!(rec->flags & FTRACE_FL_TRAMP))
2301-
continue;
2302-
2303-
/* This record had better have a trampoline */
2304-
if (FTRACE_WARN_ON(!(rec->flags & FTRACE_FL_TRAMP_EN)))
2305-
return -1;
2306-
2307-
ret = add_hash_entry(ops->tramp_hash, rec->ip);
2308-
if (ret < 0)
2309-
return ret;
2310-
}
2311-
} while_for_each_ftrace_rec();
2312-
2313-
/* The number of recs in the hash must match nr_trampolines */
2314-
if (FTRACE_WARN_ON(ops->tramp_hash->count != ops->nr_trampolines))
2315-
pr_warn("count=%ld trampolines=%d\n",
2316-
ops->tramp_hash->count,
2317-
ops->nr_trampolines);
2318-
2319-
return 0;
2320-
}
2321-
2322-
static int ftrace_save_tramp_hashes(void)
2323-
{
2324-
struct ftrace_ops *op;
2325-
int ret;
2326-
2327-
/*
2328-
* Now that any trampoline is being used, we need to save the
2329-
* hashes for the ops that have them. This allows the mapping
2330-
* back from the record to the ops that has the trampoline to
2331-
* know what code is being replaced. Modifying code must always
2332-
* verify what it is changing.
2333-
*/
2334-
do_for_each_ftrace_op(op, ftrace_ops_list) {
2335-
2336-
/* The tramp_hash is recreated each time. */
2337-
free_ftrace_hash(op->tramp_hash);
2338-
op->tramp_hash = NULL;
2339-
2340-
if (op->nr_trampolines) {
2341-
ret = ftrace_save_ops_tramp_hash(op);
2342-
if (ret)
2343-
return ret;
2344-
}
2345-
2346-
} while_for_each_ftrace_op(op);
2347-
2348-
return 0;
2349-
}
2350-
23512275
static void ftrace_run_update_code(int command)
23522276
{
23532277
int ret;
@@ -2367,9 +2291,6 @@ static void ftrace_run_update_code(int command)
23672291

23682292
ret = ftrace_arch_code_modify_post_process();
23692293
FTRACE_WARN_ON(ret);
2370-
2371-
ret = ftrace_save_tramp_hashes();
2372-
FTRACE_WARN_ON(ret);
23732294
}
23742295

23752296
static void ftrace_run_modify_code(struct ftrace_ops *ops, int command)
@@ -2489,8 +2410,16 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command)
24892410
ops->flags |= FTRACE_OPS_FL_REMOVING;
24902411
removed_ops = ops;
24912412

2413+
/* The trampoline logic checks the old hashes */
2414+
ops->old_hash.filter_hash = ops->func_hash->filter_hash;
2415+
ops->old_hash.notrace_hash = ops->func_hash->notrace_hash;
2416+
24922417
ftrace_run_update_code(command);
24932418

2419+
ops->old_hash.filter_hash = NULL;
2420+
ops->old_hash.notrace_hash = NULL;
2421+
2422+
removed_ops = NULL;
24942423
ops->flags &= ~FTRACE_OPS_FL_REMOVING;
24952424

24962425
/*
@@ -3017,7 +2946,7 @@ static int t_show(struct seq_file *m, void *v)
30172946
struct ftrace_ops *ops;
30182947

30192948
ops = ftrace_find_tramp_ops_any(rec);
3020-
if (ops && ops->trampoline)
2949+
if (ops)
30212950
seq_printf(m, "\ttramp: %pS",
30222951
(void *)ops->trampoline);
30232952
else

0 commit comments

Comments
 (0)