| @@ -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) |