diff options
Diffstat (limited to 'kernel/trace/ftrace.c')
| -rw-r--r-- | kernel/trace/ftrace.c | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index 65b9e863056b..b17a30350f06 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
| @@ -1320,6 +1320,224 @@ static struct file_operations ftrace_notrace_fops = { | |||
| 1320 | .release = ftrace_notrace_release, | 1320 | .release = ftrace_notrace_release, |
| 1321 | }; | 1321 | }; |
| 1322 | 1322 | ||
| 1323 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | ||
| 1324 | |||
| 1325 | static DEFINE_MUTEX(graph_lock); | ||
| 1326 | |||
| 1327 | int ftrace_graph_count; | ||
| 1328 | unsigned long ftrace_graph_funcs[FTRACE_GRAPH_MAX_FUNCS] __read_mostly; | ||
| 1329 | |||
| 1330 | static void * | ||
| 1331 | g_next(struct seq_file *m, void *v, loff_t *pos) | ||
| 1332 | { | ||
| 1333 | unsigned long *array = m->private; | ||
| 1334 | int index = *pos; | ||
| 1335 | |||
| 1336 | (*pos)++; | ||
| 1337 | |||
| 1338 | if (index >= ftrace_graph_count) | ||
| 1339 | return NULL; | ||
| 1340 | |||
| 1341 | return &array[index]; | ||
| 1342 | } | ||
| 1343 | |||
| 1344 | static void *g_start(struct seq_file *m, loff_t *pos) | ||
| 1345 | { | ||
| 1346 | void *p = NULL; | ||
| 1347 | |||
| 1348 | mutex_lock(&graph_lock); | ||
| 1349 | |||
| 1350 | p = g_next(m, p, pos); | ||
| 1351 | |||
| 1352 | return p; | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | static void g_stop(struct seq_file *m, void *p) | ||
| 1356 | { | ||
| 1357 | mutex_unlock(&graph_lock); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | static int g_show(struct seq_file *m, void *v) | ||
| 1361 | { | ||
| 1362 | unsigned long *ptr = v; | ||
| 1363 | char str[KSYM_SYMBOL_LEN]; | ||
| 1364 | |||
| 1365 | if (!ptr) | ||
| 1366 | return 0; | ||
| 1367 | |||
| 1368 | kallsyms_lookup(*ptr, NULL, NULL, NULL, str); | ||
| 1369 | |||
| 1370 | seq_printf(m, "%s\n", str); | ||
| 1371 | |||
| 1372 | return 0; | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | static struct seq_operations ftrace_graph_seq_ops = { | ||
| 1376 | .start = g_start, | ||
| 1377 | .next = g_next, | ||
| 1378 | .stop = g_stop, | ||
| 1379 | .show = g_show, | ||
| 1380 | }; | ||
| 1381 | |||
| 1382 | static int | ||
| 1383 | ftrace_graph_open(struct inode *inode, struct file *file) | ||
| 1384 | { | ||
| 1385 | int ret = 0; | ||
| 1386 | |||
| 1387 | if (unlikely(ftrace_disabled)) | ||
| 1388 | return -ENODEV; | ||
| 1389 | |||
| 1390 | mutex_lock(&graph_lock); | ||
| 1391 | if ((file->f_mode & FMODE_WRITE) && | ||
| 1392 | !(file->f_flags & O_APPEND)) { | ||
| 1393 | ftrace_graph_count = 0; | ||
| 1394 | memset(ftrace_graph_funcs, 0, sizeof(ftrace_graph_funcs)); | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | if (file->f_mode & FMODE_READ) { | ||
| 1398 | ret = seq_open(file, &ftrace_graph_seq_ops); | ||
| 1399 | if (!ret) { | ||
| 1400 | struct seq_file *m = file->private_data; | ||
| 1401 | m->private = ftrace_graph_funcs; | ||
| 1402 | } | ||
| 1403 | } else | ||
| 1404 | file->private_data = ftrace_graph_funcs; | ||
| 1405 | mutex_unlock(&graph_lock); | ||
| 1406 | |||
| 1407 | return ret; | ||
| 1408 | } | ||
| 1409 | |||
| 1410 | static ssize_t | ||
| 1411 | ftrace_graph_read(struct file *file, char __user *ubuf, | ||
| 1412 | size_t cnt, loff_t *ppos) | ||
| 1413 | { | ||
| 1414 | if (file->f_mode & FMODE_READ) | ||
| 1415 | return seq_read(file, ubuf, cnt, ppos); | ||
| 1416 | else | ||
| 1417 | return -EPERM; | ||
| 1418 | } | ||
| 1419 | |||
| 1420 | static int | ||
| 1421 | ftrace_set_func(unsigned long *array, int idx, char *buffer) | ||
| 1422 | { | ||
| 1423 | char str[KSYM_SYMBOL_LEN]; | ||
| 1424 | struct dyn_ftrace *rec; | ||
| 1425 | struct ftrace_page *pg; | ||
| 1426 | int found = 0; | ||
| 1427 | int i; | ||
| 1428 | |||
| 1429 | if (ftrace_disabled) | ||
| 1430 | return -ENODEV; | ||
| 1431 | |||
| 1432 | /* should not be called from interrupt context */ | ||
| 1433 | spin_lock(&ftrace_lock); | ||
| 1434 | |||
| 1435 | for (pg = ftrace_pages_start; pg; pg = pg->next) { | ||
| 1436 | for (i = 0; i < pg->index; i++) { | ||
| 1437 | rec = &pg->records[i]; | ||
| 1438 | |||
| 1439 | if (rec->flags & (FTRACE_FL_FAILED | FTRACE_FL_FREE)) | ||
| 1440 | continue; | ||
| 1441 | |||
| 1442 | kallsyms_lookup(rec->ip, NULL, NULL, NULL, str); | ||
| 1443 | if (strcmp(str, buffer) == 0) { | ||
| 1444 | found = 1; | ||
| 1445 | array[idx] = rec->ip; | ||
| 1446 | break; | ||
| 1447 | } | ||
| 1448 | } | ||
| 1449 | } | ||
| 1450 | spin_unlock(&ftrace_lock); | ||
| 1451 | |||
| 1452 | return found ? 0 : -EINVAL; | ||
| 1453 | } | ||
| 1454 | |||
| 1455 | static ssize_t | ||
| 1456 | ftrace_graph_write(struct file *file, const char __user *ubuf, | ||
| 1457 | size_t cnt, loff_t *ppos) | ||
| 1458 | { | ||
| 1459 | unsigned char buffer[FTRACE_BUFF_MAX+1]; | ||
| 1460 | unsigned long *array; | ||
| 1461 | size_t read = 0; | ||
| 1462 | ssize_t ret; | ||
| 1463 | int index = 0; | ||
| 1464 | char ch; | ||
| 1465 | |||
| 1466 | if (!cnt || cnt < 0) | ||
| 1467 | return 0; | ||
| 1468 | |||
| 1469 | mutex_lock(&graph_lock); | ||
| 1470 | |||
| 1471 | if (ftrace_graph_count >= FTRACE_GRAPH_MAX_FUNCS) { | ||
| 1472 | ret = -EBUSY; | ||
| 1473 | goto out; | ||
| 1474 | } | ||
| 1475 | |||
| 1476 | if (file->f_mode & FMODE_READ) { | ||
| 1477 | struct seq_file *m = file->private_data; | ||
| 1478 | array = m->private; | ||
| 1479 | } else | ||
| 1480 | array = file->private_data; | ||
| 1481 | |||
| 1482 | ret = get_user(ch, ubuf++); | ||
| 1483 | if (ret) | ||
| 1484 | goto out; | ||
| 1485 | read++; | ||
| 1486 | cnt--; | ||
| 1487 | |||
| 1488 | /* skip white space */ | ||
| 1489 | while (cnt && isspace(ch)) { | ||
| 1490 | ret = get_user(ch, ubuf++); | ||
| 1491 | if (ret) | ||
| 1492 | goto out; | ||
| 1493 | read++; | ||
| 1494 | cnt--; | ||
| 1495 | } | ||
| 1496 | |||
| 1497 | if (isspace(ch)) { | ||
| 1498 | *ppos += read; | ||
| 1499 | ret = read; | ||
| 1500 | goto out; | ||
| 1501 | } | ||
| 1502 | |||
| 1503 | while (cnt && !isspace(ch)) { | ||
| 1504 | if (index < FTRACE_BUFF_MAX) | ||
| 1505 | buffer[index++] = ch; | ||
| 1506 | else { | ||
| 1507 | ret = -EINVAL; | ||
| 1508 | goto out; | ||
| 1509 | } | ||
| 1510 | ret = get_user(ch, ubuf++); | ||
| 1511 | if (ret) | ||
| 1512 | goto out; | ||
| 1513 | read++; | ||
| 1514 | cnt--; | ||
| 1515 | } | ||
| 1516 | buffer[index] = 0; | ||
| 1517 | |||
| 1518 | /* we allow only one at a time */ | ||
| 1519 | ret = ftrace_set_func(array, ftrace_graph_count, buffer); | ||
| 1520 | if (ret) | ||
| 1521 | goto out; | ||
| 1522 | |||
| 1523 | ftrace_graph_count++; | ||
| 1524 | |||
| 1525 | file->f_pos += read; | ||
| 1526 | |||
| 1527 | ret = read; | ||
| 1528 | out: | ||
| 1529 | mutex_unlock(&graph_lock); | ||
| 1530 | |||
| 1531 | return ret; | ||
| 1532 | } | ||
| 1533 | |||
| 1534 | static const struct file_operations ftrace_graph_fops = { | ||
| 1535 | .open = ftrace_graph_open, | ||
| 1536 | .read = ftrace_graph_read, | ||
| 1537 | .write = ftrace_graph_write, | ||
| 1538 | }; | ||
| 1539 | #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ | ||
| 1540 | |||
| 1323 | static __init int ftrace_init_dyn_debugfs(struct dentry *d_tracer) | 1541 | static __init int ftrace_init_dyn_debugfs(struct dentry *d_tracer) |
| 1324 | { | 1542 | { |
| 1325 | struct dentry *entry; | 1543 | struct dentry *entry; |
| @@ -1347,6 +1565,15 @@ static __init int ftrace_init_dyn_debugfs(struct dentry *d_tracer) | |||
| 1347 | pr_warning("Could not create debugfs " | 1565 | pr_warning("Could not create debugfs " |
| 1348 | "'set_ftrace_notrace' entry\n"); | 1566 | "'set_ftrace_notrace' entry\n"); |
| 1349 | 1567 | ||
| 1568 | #ifdef CONFIG_FUNCTION_GRAPH_TRACER | ||
| 1569 | entry = debugfs_create_file("set_graph_function", 0444, d_tracer, | ||
| 1570 | NULL, | ||
| 1571 | &ftrace_graph_fops); | ||
| 1572 | if (!entry) | ||
| 1573 | pr_warning("Could not create debugfs " | ||
| 1574 | "'set_graph_function' entry\n"); | ||
| 1575 | #endif /* CONFIG_FUNCTION_GRAPH_TRACER */ | ||
| 1576 | |||
| 1350 | return 0; | 1577 | return 0; |
| 1351 | } | 1578 | } |
| 1352 | 1579 | ||
