diff options
Diffstat (limited to 'kernel/trace/ftrace.c')
-rw-r--r-- | kernel/trace/ftrace.c | 158 |
1 files changed, 142 insertions, 16 deletions
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); |