| // 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 "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; | |
| 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, 500); | |
| } | |
| 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, 500); | |
| 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_addr_map(thread, | |
| PERF_RECORD_MISC_USER, MAP__FUNCTION, | |
| (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_addr_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; | |
| } |