Introduce FTRACE_OPS_FL_IPMODIFY to avoid conflict among ftrace users who may modify regs->ip to change the execution path. If two or more users modify the regs->ip on the same function entry, one of them will be broken. So they must add IPMODIFY flag and make sure that ftrace_set_filter_ip() succeeds. Note that ftrace doesn't allow ftrace_ops which has IPMODIFY flag to have notrace hash, and the ftrace_ops must have a filter hash (so that the ftrace_ops can hook only specific entries), because it strongly depends on the address and must be allowed for only few selected functions. Link: http://lkml.kernel.org/r/20141121102516.11844.27829.stgit@localhost.localdomain
Showing the changes to kernel/trace (1 of 3 files). Show all
Unified · Split
| @@ -1358,6 +1358,9 @@ ftrace_hash_rec_disable_modify(struct ftrace_ops *ops, int filter_hash); · 1357 unmodified lines | ||
| 1358 | 1358 | static void |
| 1359 | 1359 | ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, int filter_hash); |
| 1360 | 1360 | |
| 1361 | +static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops, | |
| 1362 | + struct ftrace_hash *new_hash); | |
| 1363 | + | |
| 1361 | 1364 | static int |
| 1362 | 1365 | ftrace_hash_move(struct ftrace_ops *ops, int enable, |
| 1363 | 1366 | struct ftrace_hash **dst, struct ftrace_hash *src) |
| @@ -1368,8 +1371,13 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable, · 4 unmodified lines | ||
| 1368 | 1371 | struct ftrace_hash *new_hash; |
| 1369 | 1372 | int size = src->count; |
| 1370 | 1373 | int bits = 0; |
| 1374 | + int ret; | |
| 1371 | 1375 | int i; |
| 1372 | 1376 | |
| 1377 | + /* Reject setting notrace hash on IPMODIFY ftrace_ops */ | |
| 1378 | + if (ops->flags & FTRACE_OPS_FL_IPMODIFY && !enable) | |
| 1379 | + return -EINVAL; | |
| 1380 | + | |
| 1373 | 1381 | /* |
| 1374 | 1382 | * If the new source is empty, just free dst and assign it |
| 1375 | 1383 | * the empty_hash. |
| @@ -1403,6 +1411,16 @@ ftrace_hash_move(struct ftrace_ops *ops, int enable, · 27 unmodified lines | ||
| 1403 | 1411 | } |
| 1404 | 1412 | |
| 1405 | 1413 | update: |
| 1414 | + /* Make sure this can be applied if it is IPMODIFY ftrace_ops */ | |
| 1415 | + if (enable) { | |
| 1416 | + /* IPMODIFY should be updated only when filter_hash updating */ | |
| 1417 | + ret = ftrace_hash_ipmodify_update(ops, new_hash); | |
| 1418 | + if (ret < 0) { | |
| 1419 | + free_ftrace_hash(new_hash); | |
| 1420 | + return ret; | |
| 1421 | + } | |
| 1422 | + } | |
| 1423 | + | |
| 1406 | 1424 | /* |
| 1407 | 1425 | * Remove the current set, update the hash and add |
| 1408 | 1426 | * them back. |
| @@ -1767,6 +1785,114 @@ static void ftrace_hash_rec_enable_modify(struct ftrace_ops *ops, · 358 unmodified lines | ||
| 1767 | 1785 | ftrace_hash_rec_update_modify(ops, filter_hash, 1); |
| 1768 | 1786 | } |
| 1769 | 1787 | |
| 1788 | +/* | |
| 1789 | + * Try to update IPMODIFY flag on each ftrace_rec. Return 0 if it is OK | |
| 1790 | + * or no-needed to update, -EBUSY if it detects a conflict of the flag | |
| 1791 | + * on a ftrace_rec, and -EINVAL if the new_hash tries to trace all recs. | |
| 1792 | + * Note that old_hash and new_hash has below meanings | |
| 1793 | + * - If the hash is NULL, it hits all recs (if IPMODIFY is set, this is rejected) | |
| 1794 | + * - If the hash is EMPTY_HASH, it hits nothing | |
| 1795 | + * - Anything else hits the recs which match the hash entries. | |
| 1796 | + */ | |
| 1797 | +static int __ftrace_hash_update_ipmodify(struct ftrace_ops *ops, | |
| 1798 | + struct ftrace_hash *old_hash, | |
| 1799 | + struct ftrace_hash *new_hash) | |
| 1800 | +{ | |
| 1801 | + struct ftrace_page *pg; | |
| 1802 | + struct dyn_ftrace *rec, *end = NULL; | |
| 1803 | + int in_old, in_new; | |
| 1804 | + | |
| 1805 | + /* Only update if the ops has been registered */ | |
| 1806 | + if (!(ops->flags & FTRACE_OPS_FL_ENABLED)) | |
| 1807 | + return 0; | |
| 1808 | + | |
| 1809 | + if (!(ops->flags & FTRACE_OPS_FL_IPMODIFY)) | |
| 1810 | + return 0; | |
| 1811 | + | |
| 1812 | + /* | |
| 1813 | + * Since the IPMODIFY is a very address sensitive action, we do not | |
| 1814 | + * allow ftrace_ops to set all functions to new hash. | |
| 1815 | + */ | |
| 1816 | + if (!new_hash || !old_hash) | |
| 1817 | + return -EINVAL; | |
| 1818 | + | |
| 1819 | + /* Update rec->flags */ | |
| 1820 | + do_for_each_ftrace_rec(pg, rec) { | |
| 1821 | + /* We need to update only differences of filter_hash */ | |
| 1822 | + in_old = !!ftrace_lookup_ip(old_hash, rec->ip); | |
| 1823 | + in_new = !!ftrace_lookup_ip(new_hash, rec->ip); | |
| 1824 | + if (in_old == in_new) | |
| 1825 | + continue; | |
| 1826 | + | |
| 1827 | + if (in_new) { | |
| 1828 | + /* New entries must ensure no others are using it */ | |
| 1829 | + if (rec->flags & FTRACE_FL_IPMODIFY) | |
| 1830 | + goto rollback; | |
| 1831 | + rec->flags |= FTRACE_FL_IPMODIFY; | |
| 1832 | + } else /* Removed entry */ | |
| 1833 | + rec->flags &= ~FTRACE_FL_IPMODIFY; | |
| 1834 | + } while_for_each_ftrace_rec(); | |
| 1835 | + | |
| 1836 | + return 0; | |
| 1837 | + | |
| 1838 | +rollback: | |
| 1839 | + end = rec; | |
| 1840 | + | |
| 1841 | + /* Roll back what we did above */ | |
| 1842 | + do_for_each_ftrace_rec(pg, rec) { | |
| 1843 | + if (rec == end) | |
| 1844 | + goto err_out; | |
| 1845 | + | |
| 1846 | + in_old = !!ftrace_lookup_ip(old_hash, rec->ip); | |
| 1847 | + in_new = !!ftrace_lookup_ip(new_hash, rec->ip); | |
| 1848 | + if (in_old == in_new) | |
| 1849 | + continue; | |
| 1850 | + | |
| 1851 | + if (in_new) | |
| 1852 | + rec->flags &= ~FTRACE_FL_IPMODIFY; | |
| 1853 | + else | |
| 1854 | + rec->flags |= FTRACE_FL_IPMODIFY; | |
| 1855 | + } while_for_each_ftrace_rec(); | |
| 1856 | + | |
| 1857 | +err_out: | |
| 1858 | + return -EBUSY; | |
| 1859 | +} | |
| 1860 | + | |
| 1861 | +static int ftrace_hash_ipmodify_enable(struct ftrace_ops *ops) | |
| 1862 | +{ | |
| 1863 | + struct ftrace_hash *hash = ops->func_hash->filter_hash; | |
| 1864 | + | |
| 1865 | + if (ftrace_hash_empty(hash)) | |
| 1866 | + hash = NULL; | |
| 1867 | + | |
| 1868 | + return __ftrace_hash_update_ipmodify(ops, EMPTY_HASH, hash); | |
| 1869 | +} | |
| 1870 | + | |
| 1871 | +/* Disabling always succeeds */ | |
| 1872 | +static void ftrace_hash_ipmodify_disable(struct ftrace_ops *ops) | |
| 1873 | +{ | |
| 1874 | + struct ftrace_hash *hash = ops->func_hash->filter_hash; | |
| 1875 | + | |
| 1876 | + if (ftrace_hash_empty(hash)) | |
| 1877 | + hash = NULL; | |
| 1878 | + | |
| 1879 | + __ftrace_hash_update_ipmodify(ops, hash, EMPTY_HASH); | |
| 1880 | +} | |
| 1881 | + | |
| 1882 | +static int ftrace_hash_ipmodify_update(struct ftrace_ops *ops, | |
| 1883 | + struct ftrace_hash *new_hash) | |
| 1884 | +{ | |
| 1885 | + struct ftrace_hash *old_hash = ops->func_hash->filter_hash; | |
| 1886 | + | |
| 1887 | + if (ftrace_hash_empty(old_hash)) | |
| 1888 | + old_hash = NULL; | |
| 1889 | + | |
| 1890 | + if (ftrace_hash_empty(new_hash)) | |
| 1891 | + new_hash = NULL; | |
| 1892 | + | |
| 1893 | + return __ftrace_hash_update_ipmodify(ops, old_hash, new_hash); | |
| 1894 | +} | |
| 1895 | + | |
| 1770 | 1896 | static void print_ip_ins(const char *fmt, unsigned char *p) |
| 1771 | 1897 | { |
| 1772 | 1898 | int i; |
| @@ -2436,6 +2562,15 @@ static int ftrace_startup(struct ftrace_ops *ops, int command) · 663 unmodified lines | ||
| 2436 | 2562 | */ |
| 2437 | 2563 | ops->flags |= FTRACE_OPS_FL_ENABLED | FTRACE_OPS_FL_ADDING; |
| 2438 | 2564 | |
| 2565 | + ret = ftrace_hash_ipmodify_enable(ops); | |
| 2566 | + if (ret < 0) { | |
| 2567 | + /* Rollback registration process */ | |
| 2568 | + __unregister_ftrace_function(ops); | |
| 2569 | + ftrace_start_up--; | |
| 2570 | + ops->flags &= ~FTRACE_OPS_FL_ENABLED; | |
| 2571 | + return ret; | |
| 2572 | + } | |
| 2573 | + | |
| 2439 | 2574 | ftrace_hash_rec_enable(ops, 1); |
| 2440 | 2575 | |
| 2441 | 2576 | ftrace_startup_enable(command); |
| @@ -2464,6 +2599,8 @@ static int ftrace_shutdown(struct ftrace_ops *ops, int command) · 22 unmodified lines | ||
| 2464 | 2599 | */ |
| 2465 | 2600 | WARN_ON_ONCE(ftrace_start_up < 0); |
| 2466 | 2601 | |
| 2602 | + /* Disabling ipmodify never fails */ | |
| 2603 | + ftrace_hash_ipmodify_disable(ops); | |
| 2467 | 2604 | ftrace_hash_rec_disable(ops, 1); |
| 2468 | 2605 | |
| 2469 | 2606 | ops->flags &= ~FTRACE_OPS_FL_ENABLED; |
| @@ -3058,9 +3195,10 @@ static int t_show(struct seq_file *m, void *v) · 588 unmodified lines | ||
| 3058 | 3195 | if (iter->flags & FTRACE_ITER_ENABLED) { |
| 3059 | 3196 | struct ftrace_ops *ops = NULL; |
| 3060 | 3197 | |
| 3061 | − seq_printf(m, " (%ld)%s", | |
| 3198 | + seq_printf(m, " (%ld)%s%s", | |
| 3062 | 3199 | ftrace_rec_count(rec), |
| 3063 | − rec->flags & FTRACE_FL_REGS ? " R" : " "); | |
| 3200 | + rec->flags & FTRACE_FL_REGS ? " R" : " ", | |
| 3201 | + rec->flags & FTRACE_FL_IPMODIFY ? " I" : " "); | |
| 3064 | 3202 | if (rec->flags & FTRACE_FL_TRAMP_EN) { |
| 3065 | 3203 | ops = ftrace_find_tramp_ops_any(rec); |
| 3066 | 3204 | if (ops) |