| // SPDX-License-Identifier: GPL-2.0 |
| #include <inttypes.h> |
| #include <unistd.h> |
| #include <sys/syscall.h> |
| #include <sys/types.h> |
| #include <sys/mman.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include "debug.h" |
| #include "tests.h" |
| #include "machine.h" |
| #include "thread_map.h" |
| #include "map.h" |
| #include "symbol.h" |
| #include "thread.h" |
| #include "util.h" |
| |
| #define THREADS 4 |
| |
| static int go_away; |
| |
| struct thread_data { |
| pthread_t pt; |
| pid_t tid; |
| void *map; |
| int ready[2]; |
| }; |
| |
| static struct thread_data threads[THREADS]; |
| |
| static int thread_init(struct thread_data *td) |
| { |
| void *map; |
| |
| map = mmap(NULL, page_size, |
| PROT_READ|PROT_WRITE|PROT_EXEC, |
| MAP_SHARED|MAP_ANONYMOUS, -1, 0); |
| |
| if (map == MAP_FAILED) { |
| perror("mmap failed"); |
| return -1; |
| } |
| |
| td->map = map; |
| td->tid = syscall(SYS_gettid); |
| |
| pr_debug("tid = %d, map = %p\n", td->tid, map); |
| return 0; |
| } |
| |
| static void *thread_fn(void *arg) |
| { |
| struct thread_data *td = arg; |
| ssize_t ret; |
| int go = 0; |
| |
| if (thread_init(td)) |
| return NULL; |
| |
| /* Signal thread_create thread is initialized. */ |
| ret = write(td->ready[1], &go, sizeof(int)); |
| if (ret != sizeof(int)) { |
| pr_err("failed to notify\n"); |
| return NULL; |
| } |
| |
| while (!go_away) { |
| /* Waiting for main thread to kill us. */ |
| usleep(100); |
| } |
| |
| munmap(td->map, page_size); |
| return NULL; |
| } |
| |
| static int thread_create(int i) |
| { |
| struct thread_data *td = &threads[i]; |
| int err, go; |
| |
| if (pipe(td->ready)) |
| return -1; |
| |
| err = pthread_create(&td->pt, NULL, thread_fn, td); |
| if (!err) { |
| /* Wait for thread initialization. */ |
| ssize_t ret = read(td->ready[0], &go, sizeof(int)); |
| err = ret != sizeof(int); |
| } |
| |
| close(td->ready[0]); |
| close(td->ready[1]); |
| return err; |
| } |
| |
| static int threads_create(void) |
| { |
| struct thread_data *td0 = &threads[0]; |
| int i, err = 0; |
| |
| go_away = 0; |
| |
| /* 0 is main thread */ |
| if (thread_init(td0)) |
| return -1; |
| |
| for (i = 1; !err && i < THREADS; i++) |
| err = thread_create(i); |
| |
| return err; |
| } |
| |
| static int threads_destroy(void) |
| { |
| struct thread_data *td0 = &threads[0]; |
| int i, err = 0; |
| |
| /* cleanup the main thread */ |
| munmap(td0->map, page_size); |
| |
| go_away = 1; |
| |
| for (i = 1; !err && i < THREADS; i++) |
| err = pthread_join(threads[i].pt, NULL); |
| |
| return err; |
| } |
| |
| typedef int (*synth_cb)(struct machine *machine); |
| |
| static int synth_all(struct machine *machine) |
| { |
| return perf_event__synthesize_threads(NULL, |
| perf_event__process, |
| machine, 0, 1); |
| } |
| |
| static int synth_process(struct machine *machine) |
| { |
| struct thread_map *map; |
| int err; |
| |
| map = thread_map__new_by_pid(getpid()); |
| |
| err = perf_event__synthesize_thread_map(NULL, map, |
| perf_event__process, |
| machine, 0); |
| |
| thread_map__put(map); |
| return err; |
| } |
| |
| static int mmap_events(synth_cb synth) |
| { |
| struct machine *machine; |
| int err, i; |
| |
| /* |
| * The threads_create will not return before all threads |
| * are spawned and all created memory map. |
| * |
| * They will loop until threads_destroy is called, so we |
| * can safely run synthesizing function. |
| */ |
| TEST_ASSERT_VAL("failed to create threads", !threads_create()); |
| |
| machine = machine__new_host(); |
| |
| dump_trace = verbose > 1 ? 1 : 0; |
| |
| err = synth(machine); |
| |
| dump_trace = 0; |
| |
| TEST_ASSERT_VAL("failed to destroy threads", !threads_destroy()); |
| TEST_ASSERT_VAL("failed to synthesize maps", !err); |
| |
| /* |
| * All data is synthesized, try to find map for each |
| * thread object. |
| */ |
| for (i = 0; i < THREADS; i++) { |
| struct thread_data *td = &threads[i]; |
| struct addr_location al; |
| struct thread *thread; |
| |
| thread = machine__findnew_thread(machine, getpid(), td->tid); |
| |
| pr_debug("looking for map %p\n", td->map); |
| |
| thread__find_map(thread, PERF_RECORD_MISC_USER, |
| (unsigned long) (td->map + 1), &al); |
| |
| thread__put(thread); |
| |
| if (!al.map) { |
| pr_debug("failed, couldn't find map\n"); |
| err = -1; |
| break; |
| } |
| |
| pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start); |
| } |
| |
| machine__delete_threads(machine); |
| machine__delete(machine); |
| return err; |
| } |
| |
| /* |
| * This test creates 'THREADS' number of threads (including |
| * main thread) and each thread creates memory map. |
| * |
| * When threads are created, we synthesize them with both |
| * (separate tests): |
| * perf_event__synthesize_thread_map (process based) |
| * perf_event__synthesize_threads (global) |
| * |
| * We test we can find all memory maps via: |
| * thread__find_map |
| * |
| * by using all thread objects. |
| */ |
| int test__mmap_thread_lookup(struct test *test __maybe_unused, int subtest __maybe_unused) |
| { |
| /* perf_event__synthesize_threads synthesize */ |
| TEST_ASSERT_VAL("failed with sythesizing all", |
| !mmap_events(synth_all)); |
| |
| /* perf_event__synthesize_thread_map synthesize */ |
| TEST_ASSERT_VAL("failed with sythesizing process", |
| !mmap_events(synth_process)); |
| |
| return 0; |
| } |