diff options
| -rw-r--r-- | include/linux/ftrace.h | 8 | ||||
| -rw-r--r-- | kernel/trace/ftrace.c | 158 |
2 files changed, 148 insertions, 18 deletions
diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 6658a51390fe..ab1c46e70bb6 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h | |||
| @@ -37,6 +37,7 @@ struct ftrace_ops { | |||
| 37 | #ifdef CONFIG_DYNAMIC_FTRACE | 37 | #ifdef CONFIG_DYNAMIC_FTRACE |
| 38 | struct ftrace_hash *notrace_hash; | 38 | struct ftrace_hash *notrace_hash; |
| 39 | struct ftrace_hash *filter_hash; | 39 | struct ftrace_hash *filter_hash; |
| 40 | unsigned long flags; | ||
| 40 | #endif | 41 | #endif |
| 41 | }; | 42 | }; |
| 42 | 43 | ||
| @@ -152,10 +153,13 @@ extern void unregister_ftrace_function_probe_all(char *glob); | |||
| 152 | extern int ftrace_text_reserved(void *start, void *end); | 153 | extern int ftrace_text_reserved(void *start, void *end); |
| 153 | 154 | ||
| 154 | enum { | 155 | enum { |
| 155 | FTRACE_FL_FREE = (1 << 0), | 156 | FTRACE_FL_ENABLED = (1 << 30), |
| 156 | FTRACE_FL_ENABLED = (1 << 1), | 157 | FTRACE_FL_FREE = (1 << 31), |
| 157 | }; | 158 | }; |
| 158 | 159 | ||
| 160 | #define FTRACE_FL_MASK (0x3UL << 30) | ||
| 161 | #define FTRACE_REF_MAX ((1 << 30) - 1) | ||
| 162 | |||
| 159 | struct dyn_ftrace { | 163 | struct dyn_ftrace { |
| 160 | union { | 164 | union { |
| 161 | unsigned long ip; /* address of mcount call-site */ | 165 | unsigned long ip; /* address of mcount call-site */ |
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 46f08264980b..5dd332cc5aa8 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
| @@ -890,6 +890,10 @@ static const struct ftrace_hash empty_hash = { | |||
| 890 | }; | 890 | }; |
| 891 | #define EMPTY_HASH ((struct ftrace_hash *)&empty_hash) | 891 | #define EMPTY_HASH ((struct ftrace_hash *)&empty_hash) |
| 892 | 892 | ||
| 893 | enum { | ||
| 894 | FTRACE_OPS_FL_ENABLED = 1, | ||
| 895 | }; | ||
| 896 | |||
| 893 | struct ftrace_ops global_ops = { | 897 | struct ftrace_ops global_ops = { |
| 894 | .func = ftrace_stub, | 898 | .func = ftrace_stub, |
| 895 | .notrace_hash = EMPTY_HASH, | 899 | .notrace_hash = EMPTY_HASH, |
| @@ -1161,6 +1165,105 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src) | |||
| 1161 | } \ | 1165 | } \ |
| 1162 | } | 1166 | } |
| 1163 | 1167 | ||
| 1168 | static void __ftrace_hash_rec_update(struct ftrace_ops *ops, | ||
| 1169 | int filter_hash, | ||
| 1170 | bool inc) | ||
| 1171 | { | ||
| 1172 | struct ftrace_hash *hash; | ||
| 1173 | struct ftrace_hash *other_hash; | ||
| 1174 | struct ftrace_page *pg; | ||
| 1175 | struct dyn_ftrace *rec; | ||
| 1176 | int count = 0; | ||
| 1177 | int all = 0; | ||
| 1178 | |||
| 1179 | /* Only update if the ops has been registered */ | ||
| 1180 | if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) | ||
| 1181 | return; | ||
| 1182 | |||
| 1183 | /* | ||
| 1184 | * In the filter_hash case: | ||
| 1185 | * If the count is zero, we update all records. | ||
| 1186 | * Otherwise we just update the items in the hash. | ||
| 1187 | * | ||
| 1188 | * In the notrace_hash case: | ||
| 1189 | * We enable the update in the hash. | ||
| 1190 | * As disabling notrace means enabling the tracing, | ||
| 1191 | * and enabling notrace means disabling, the inc variable | ||
| 1192 | * gets inversed. | ||
| 1193 | */ | ||
| 1194 | if (filter_hash) { | ||
| 1195 | hash = ops->filter_hash; | ||
| 1196 | other_hash = ops->notrace_hash; | ||
| 1197 | if (!hash->count) | ||
| 1198 | all = 1; | ||
| 1199 | } else { | ||
| 1200 | inc = !inc; | ||
| 1201 | hash = ops->notrace_hash; | ||
| 1202 | other_hash = ops->filter_hash; | ||
| 1203 | /* | ||
| 1204 | * If the notrace hash has no items, | ||
| 1205 | * then there's nothing to do. | ||
| 1206 | */ | ||
| 1207 | if (!hash->count) | ||
| 1208 | return; | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | do_for_each_ftrace_rec(pg, rec) { | ||
| 1212 | int in_other_hash = 0; | ||
| 1213 | int in_hash = 0; | ||
| 1214 | int match = 0; | ||
| 1215 | |||
| 1216 | if (all) { | ||
| 1217 | /* | ||
| 1218 | * Only the filter_hash affects all records. | ||
| 1219 | * Update if the record is not in the notrace hash. | ||
| 1220 | */ | ||
| 1221 | if (!ftrace_lookup_ip(other_hash, rec->ip)) | ||
| 1222 | match = 1; | ||
| 1223 | } else { | ||
| 1224 | in_hash = !!ftrace_lookup_ip(hash, rec->ip); | ||
| 1225 | in_other_hash = !!ftrace_lookup_ip(other_hash, rec->ip); | ||
| 1226 | |||
| 1227 | /* | ||
| 1228 | * | ||
| 1229 | */ | ||
| 1230 | if (filter_hash && in_hash && !in_other_hash) | ||
| 1231 | match = 1; | ||
| 1232 | else if (!filter_hash && in_hash && | ||
| 1233 | (in_other_hash || !other_hash->count)) | ||
| 1234 | match = 1; | ||
| 1235 | } | ||
| 1236 | if (!match) | ||
| 1237 | continue; | ||
| 1238 | |||
| 1239 | if (inc) { | ||
| 1240 | rec->flags++; | ||
| 1241 | if (FTRACE_WARN_ON((rec->flags & ~FTRACE_FL_MASK) == FTRACE_REF_MAX)) | ||
| 1242 | return; | ||
| 1243 | } else { | ||
| 1244 | if (FTRACE_WARN_ON((rec->flags & ~FTRACE_FL_MASK) == 0)) | ||
| 1245 | return; | ||
| 1246 | rec->flags--; | ||
| 1247 | } | ||
| 1248 | count++; | ||
| 1249 | /* Shortcut, if we handled all records, we are done. */ | ||
| 1250 | if (!all && count == hash->count) | ||
| 1251 | return; | ||
| 1252 | } while_for_each_ftrace_rec(); | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | static void ftrace_hash_rec_disable(struct ftrace_ops *ops, | ||
| 1256 | int filter_hash) | ||
| 1257 | { | ||
| 1258 | __ftrace_hash_rec_update(ops, filter_hash, 0); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | static void ftrace_hash_rec_enable(struct ftrace_ops *ops, | ||
| 1262 | int filter_hash) | ||
| 1263 | { | ||
| 1264 | __ftrace_hash_rec_update(ops, filter_hash, 1); | ||
| 1265 | } | ||
| 1266 | |||
| 1164 | static void ftrace_free_rec(struct dyn_ftrace *rec) | 1267 | static void ftrace_free_rec(struct dyn_ftrace *rec) |
| 1165 | { | 1268 | { |
| 1166 | rec->freelist = ftrace_free_records; | 1269 | rec->freelist = ftrace_free_records; |
| @@ -1276,26 +1379,24 @@ int ftrace_text_reserved(void *start, void *end) | |||
| 1276 | static int | 1379 | static int |
| 1277 | __ftrace_replace_code(struct dyn_ftrace *rec, int enable) | 1380 | __ftrace_replace_code(struct dyn_ftrace *rec, int enable) |
| 1278 | { | 1381 | { |
| 1279 | struct ftrace_ops *ops = &global_ops; | ||
| 1280 | unsigned long ftrace_addr; | 1382 | unsigned long ftrace_addr; |
| 1281 | unsigned long flag = 0UL; | 1383 | unsigned long flag = 0UL; |
| 1282 | 1384 | ||
| 1283 | ftrace_addr = (unsigned long)FTRACE_ADDR; | 1385 | ftrace_addr = (unsigned long)FTRACE_ADDR; |
| 1284 | 1386 | ||
| 1285 | /* | 1387 | /* |
| 1286 | * If this record is not to be traced or we want to disable it, | 1388 | * If we are enabling tracing: |
| 1287 | * then disable it. | ||
| 1288 | * | 1389 | * |
| 1289 | * If we want to enable it and filtering is off, then enable it. | 1390 | * If the record has a ref count, then we need to enable it |
| 1391 | * because someone is using it. | ||
| 1290 | * | 1392 | * |
| 1291 | * If we want to enable it and filtering is on, enable it only if | 1393 | * Otherwise we make sure its disabled. |
| 1292 | * it's filtered | 1394 | * |
| 1395 | * If we are disabling tracing, then disable all records that | ||
| 1396 | * are enabled. | ||
| 1293 | */ | 1397 | */ |
| 1294 | if (enable && !ftrace_lookup_ip(ops->notrace_hash, rec->ip)) { | 1398 | if (enable && (rec->flags & ~FTRACE_FL_MASK)) |
| 1295 | if (!ops->filter_hash->count || | 1399 | flag = FTRACE_FL_ENABLED; |
| 1296 | ftrace_lookup_ip(ops->filter_hash, rec->ip)) | ||
| 1297 | flag = FTRACE_FL_ENABLED; | ||
| 1298 | } | ||
| 1299 | 1400 | ||
| 1300 | /* If the state of this record hasn't changed, then do nothing */ | 1401 | /* If the state of this record hasn't changed, then do nothing */ |
| 1301 | if ((rec->flags & FTRACE_FL_ENABLED) == flag) | 1402 | if ((rec->flags & FTRACE_FL_ENABLED) == flag) |
| @@ -1423,17 +1524,25 @@ static void ftrace_startup_enable(int command) | |||
| 1423 | 1524 | ||
| 1424 | static void ftrace_startup(int command) | 1525 | static void ftrace_startup(int command) |
| 1425 | { | 1526 | { |
| 1527 | struct ftrace_ops *ops = &global_ops; | ||
| 1528 | |||
| 1426 | if (unlikely(ftrace_disabled)) | 1529 | if (unlikely(ftrace_disabled)) |
| 1427 | return; | 1530 | return; |
| 1428 | 1531 | ||
| 1429 | ftrace_start_up++; | 1532 | ftrace_start_up++; |
| 1430 | command |= FTRACE_ENABLE_CALLS; | 1533 | command |= FTRACE_ENABLE_CALLS; |
| 1431 | 1534 | ||
| 1535 | ops->flags |= FTRACE_OPS_FL_ENABLED; | ||
| 1536 | if (ftrace_start_up == 1) | ||
| 1537 | ftrace_hash_rec_enable(ops, 1); | ||
| 1538 | |||
| 1432 | ftrace_startup_enable(command); | 1539 | ftrace_startup_enable(command); |
| 1433 | } | 1540 | } |
| 1434 | 1541 | ||
| 1435 | static void ftrace_shutdown(int command) | 1542 | static void ftrace_shutdown(int command) |
| 1436 | { | 1543 | { |
| 1544 | struct ftrace_ops *ops = &global_ops; | ||
| 1545 | |||
| 1437 | if (unlikely(ftrace_disabled)) | 1546 | if (unlikely(ftrace_disabled)) |
| 1438 | return; | 1547 | return; |
| 1439 | 1548 | ||
| @@ -1446,7 +1555,12 @@ static void ftrace_shutdown(int command) | |||
| 1446 | WARN_ON_ONCE(ftrace_start_up < 0); | 1555 | WARN_ON_ONCE(ftrace_start_up < 0); |
| 1447 | 1556 | ||
| 1448 | if (!ftrace_start_up) | 1557 | if (!ftrace_start_up) |
| 1558 | ftrace_hash_rec_disable(ops, 1); | ||
| 1559 | |||
| 1560 | if (!ftrace_start_up) { | ||
| 1449 | command |= FTRACE_DISABLE_CALLS; | 1561 | command |= FTRACE_DISABLE_CALLS; |
| 1562 | ops->flags &= ~FTRACE_OPS_FL_ENABLED; | ||
| 1563 | } | ||
| 1450 | 1564 | ||
| 1451 | if (saved_ftrace_func != ftrace_trace_function) { | 1565 | if (saved_ftrace_func != ftrace_trace_function) { |
| 1452 | saved_ftrace_func = ftrace_trace_function; | 1566 | saved_ftrace_func = ftrace_trace_function; |
| @@ -2668,6 +2782,7 @@ ftrace_regex_release(struct inode *inode, struct file *file) | |||
| 2668 | struct ftrace_iterator *iter; | 2782 | struct ftrace_iterator *iter; |
| 2669 | struct ftrace_hash **orig_hash; | 2783 | struct ftrace_hash **orig_hash; |
| 2670 | struct trace_parser *parser; | 2784 | struct trace_parser *parser; |
| 2785 | int filter_hash; | ||
| 2671 | int ret; | 2786 | int ret; |
| 2672 | 2787 | ||
| 2673 | mutex_lock(&ftrace_regex_lock); | 2788 | mutex_lock(&ftrace_regex_lock); |
| @@ -2687,15 +2802,26 @@ ftrace_regex_release(struct inode *inode, struct file *file) | |||
| 2687 | trace_parser_put(parser); | 2802 | trace_parser_put(parser); |
| 2688 | 2803 | ||
| 2689 | if (file->f_mode & FMODE_WRITE) { | 2804 | if (file->f_mode & FMODE_WRITE) { |
| 2690 | if (iter->flags & FTRACE_ITER_NOTRACE) | 2805 | filter_hash = !!(iter->flags & FTRACE_ITER_FILTER); |
| 2691 | orig_hash = &iter->ops->notrace_hash; | 2806 | |
| 2692 | else | 2807 | if (filter_hash) |
| 2693 | orig_hash = &iter->ops->filter_hash; | 2808 | orig_hash = &iter->ops->filter_hash; |
| 2809 | else | ||
| 2810 | orig_hash = &iter->ops->notrace_hash; | ||
| 2694 | 2811 | ||
| 2695 | mutex_lock(&ftrace_lock); | 2812 | mutex_lock(&ftrace_lock); |
| 2813 | /* | ||
| 2814 | * Remove the current set, update the hash and add | ||
| 2815 | * them back. | ||
| 2816 | */ | ||
| 2817 | ftrace_hash_rec_disable(iter->ops, filter_hash); | ||
| 2696 | ret = ftrace_hash_move(orig_hash, iter->hash); | 2818 | ret = ftrace_hash_move(orig_hash, iter->hash); |
| 2697 | if (!ret && ftrace_start_up && ftrace_enabled) | 2819 | if (!ret) { |
| 2698 | ftrace_run_update_code(FTRACE_ENABLE_CALLS); | 2820 | ftrace_hash_rec_enable(iter->ops, filter_hash); |
| 2821 | if (iter->ops->flags & FTRACE_OPS_FL_ENABLED | ||
| 2822 | && ftrace_enabled) | ||
| 2823 | ftrace_run_update_code(FTRACE_ENABLE_CALLS); | ||
| 2824 | } | ||
| 2699 | mutex_unlock(&ftrace_lock); | 2825 | mutex_unlock(&ftrace_lock); |
| 2700 | } | 2826 | } |
| 2701 | free_ftrace_hash(iter->hash); | 2827 | free_ftrace_hash(iter->hash); |
